Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c9fc1a3

Browse files
authored
Merge pull request #3663 from erik-krogh/bad-crypto
JS: Introduce query to detect biased random number generators
2 parents 86b23b2 + 7c7af8d commit c9fc1a3

7 files changed

Lines changed: 377 additions & 10 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Placeholder
8+
</p>
9+
10+
</overview>
11+
<recommendation>
12+
13+
<p>
14+
Placeholder.
15+
</p>
16+
17+
</recommendation>
18+
<example>
19+
20+
<p>
21+
Placeholder
22+
</p>
23+
24+
</example>
25+
26+
<references>
27+
<li>NIST, FIPS 140 Annex a: <a href="http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf"> Approved Security Functions</a>.</li>
28+
<li>NIST, SP 800-131A: <a href="http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf"> Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths</a>.</li>
29+
<li>OWASP: <a
30+
href="https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#rule---use-strong-approved-authenticated-encryption">Rule
31+
- Use strong approved cryptographic algorithms</a>.
32+
</li>
33+
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/3956478/understanding-randomness">Understanding “randomness”</a>.</li>
34+
</references>
35+
36+
</qhelp>
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* @name Creating biased random numbers from cryptographically secure source.
3+
* @description Some mathematical operations on random numbers can cause bias in
4+
* the results and compromise security.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @precision high
8+
* @id js/biased-cryptographic-random
9+
* @tags security
10+
* external/cwe/cwe-327
11+
*/
12+
13+
import javascript
14+
private import semmle.javascript.dataflow.internal.StepSummary
15+
private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations
16+
private import semmle.javascript.dataflow.InferredTypes
17+
18+
/**
19+
* Gets a number that is a power of 2.
20+
*/
21+
private int powerOfTwo() {
22+
result = 1
23+
or
24+
result = 2 * powerOfTwo() and
25+
not result < 0
26+
}
27+
28+
/**
29+
* Gets a node that has value 2^n for some n.
30+
*/
31+
private DataFlow::Node isPowerOfTwo() {
32+
exists(DataFlow::Node prev |
33+
prev.getIntValue() = powerOfTwo()
34+
or
35+
// Getting around the 32 bit ints in QL. These are some hex values of the form 0x10000000
36+
prev.asExpr().(NumberLiteral).getValue() =
37+
["281474976710656", "17592186044416", "1099511627776", "68719476736", "4294967296"]
38+
|
39+
result = prev.getASuccessor*()
40+
)
41+
}
42+
43+
/**
44+
* Gets a node that has value (2^n)-1 for some n.
45+
*/
46+
private DataFlow::Node isPowerOfTwoMinusOne() {
47+
exists(DataFlow::Node prev |
48+
prev.getIntValue() = powerOfTwo() - 1
49+
or
50+
// Getting around the 32 bit ints in QL. These are some hex values of the form 0xfffffff
51+
prev.asExpr().(NumberLiteral).getValue() =
52+
["281474976710655", "17592186044415", "1099511627775", "68719476735", "4294967295"]
53+
|
54+
result = prev.getASuccessor*()
55+
)
56+
}
57+
58+
/**
59+
* Gets a Buffer/TypedArray containing cryptographically secure random numbers.
60+
*/
61+
private DataFlow::SourceNode randomBufferSource() {
62+
result = DataFlow::moduleMember("crypto", ["randomBytes", "randomFillSync"]).getACall()
63+
or
64+
exists(DataFlow::CallNode call |
65+
call = DataFlow::moduleMember("crypto", ["randomFill", "randomFillSync"]) and
66+
result = call.getArgument(0).getALocalSource()
67+
)
68+
or
69+
result = DataFlow::globalVarRef("crypto").getAMethodCall("getRandomValues")
70+
or
71+
result = DataFlow::moduleImport("secure-random").getACall()
72+
or
73+
result =
74+
DataFlow::moduleImport("secure-random")
75+
.getAMethodCall(["randomArray", "randomUint8Array", "randomBuffer"])
76+
}
77+
78+
/**
79+
* Gets the pseudo-property used to track elements inside a Buffer.
80+
* The API for `Set` is close enough to the API for `Buffer` that we can reuse the type-tracking steps.
81+
*/
82+
private string prop() { result = DataFlow::PseudoProperties::setElement() }
83+
84+
/**
85+
* Gets a reference to a cryptographically secure random number produced by `source` and type tracked using `t`.
86+
*/
87+
private DataFlow::Node goodRandom(DataFlow::TypeTracker t, DataFlow::SourceNode source) {
88+
t.startInProp(prop()) and
89+
result = randomBufferSource() and
90+
result = source
91+
or
92+
// Loading a number from a `Buffer`.
93+
exists(DataFlow::TypeTracker t2 | t = t2.append(LoadStep(prop())) |
94+
// the random generators return arrays/Buffers of random numbers, we therefore track through an indexed read.
95+
exists(DataFlow::PropRead read | result = read |
96+
read.getBase() = goodRandom(t2, source) and
97+
not read.getPropertyNameExpr() instanceof Label
98+
)
99+
or
100+
// reading a number from a Buffer.
101+
exists(DataFlow::MethodCallNode call | result = call |
102+
call.getReceiver() = goodRandom(t2, source) and
103+
call
104+
.getMethodName()
105+
.regexpMatch("read(BigInt|BigUInt|Double|Float|Int|UInt)(8|16|32|64)?(BE|LE)?")
106+
)
107+
)
108+
or
109+
exists(DataFlow::TypeTracker t2 | t = t2.smallstep(goodRandom(t2, source), result))
110+
or
111+
// re-using the collection steps for `Set`.
112+
exists(DataFlow::TypeTracker t2 |
113+
result = CollectionsTypeTracking::collectionStep(goodRandom(t2, source), t, t2)
114+
)
115+
or
116+
InsecureRandomness::isAdditionalTaintStep(goodRandom(t.continue(), source), result) and
117+
// bit shifts and multiplication by powers of two are generally used for constructing larger numbers from smaller numbers.
118+
not exists(BinaryExpr binop | binop = result.asExpr() |
119+
binop.getOperator().regexpMatch(".*(<|>).*")
120+
or
121+
binop.getOperator() = "*" and isPowerOfTwo().asExpr() = binop.getAnOperand()
122+
or
123+
// string concat does not produce a number
124+
unique(InferredType type | type = binop.flow().analyze().getAType()) = TTString()
125+
)
126+
}
127+
128+
/**
129+
* Gets a reference to a cryptographically secure random number produced by `source`.
130+
*/
131+
DataFlow::Node goodRandom(DataFlow::SourceNode source) {
132+
result = goodRandom(DataFlow::TypeTracker::end(), source)
133+
}
134+
135+
/**
136+
* Gets a node that that produces a biased result from otherwise cryptographically secure random numbers produced by `source`.
137+
*/
138+
DataFlow::Node badCrypto(string description, DataFlow::SourceNode source) {
139+
// addition and multiplication - always bad when both the lhs and rhs are random.
140+
exists(BinaryExpr binop | result.asExpr() = binop |
141+
goodRandom(_).asExpr() = binop.getLeftOperand() and
142+
goodRandom(_).asExpr() = binop.getRightOperand() and
143+
goodRandom(source).asExpr() = binop.getAnOperand() and
144+
(
145+
binop.getOperator() = "+" and description = "addition"
146+
or
147+
binop.getOperator() = "*" and description = "multiplication"
148+
)
149+
)
150+
or
151+
// division - bad if result is rounded.
152+
exists(DivExpr div | result.asExpr() = div |
153+
goodRandom(source).asExpr() = div.getLeftOperand() and
154+
description = "division and rounding the result" and
155+
not div.getRightOperand() = isPowerOfTwoMinusOne().asExpr() and // division by (2^n)-1 most of the time produces a uniformly random number between 0 and 1.
156+
DataFlow::globalVarRef("Math")
157+
.getAMemberCall(["round", "floor", "ceil"])
158+
.getArgument(0)
159+
.asExpr() = div
160+
)
161+
or
162+
// modulo - only bad if not by a power of 2 - and the result is not checked for bias
163+
exists(ModExpr mod, DataFlow::Node random | result.asExpr() = mod and mod.getOperator() = "%" |
164+
description = "modulo" and
165+
goodRandom(source) = random and
166+
random.asExpr() = mod.getLeftOperand() and
167+
// division by a power of 2 is OK. E.g. if `x` is uniformly random is in the range [0..255] then `x % 32` is uniformly random in the range [0..31].
168+
not mod.getRightOperand() = isPowerOfTwo().asExpr() and
169+
// not exists a comparison that checks if the result is potentially biased.
170+
not exists(BinaryExpr comparison | comparison.getOperator() = [">", "<", "<=", ">="] |
171+
AccessPath::getAnAliasedSourceNode(random.getALocalSource())
172+
.flowsToExpr(comparison.getAnOperand())
173+
or
174+
exists(DataFlow::PropRead otherRead |
175+
otherRead = random.(DataFlow::PropRead).getBase().getALocalSource().getAPropertyRead() and
176+
not exists(otherRead.getPropertyName()) and
177+
otherRead.flowsToExpr(comparison.getAnOperand())
178+
)
179+
)
180+
)
181+
or
182+
// create a number from a string - always a bad idea.
183+
exists(DataFlow::CallNode number, StringOps::ConcatenationRoot root | result = number |
184+
number = DataFlow::globalVarRef(["Number", "parseInt", "parseFloat"]).getACall() and
185+
root = number.getArgument(0) and
186+
goodRandom(source) = root.getALeaf() and
187+
exists(root.getALeaf().getStringValue()) and
188+
description = "string concatenation"
189+
)
190+
}
191+
192+
from DataFlow::Node node, string description, DataFlow::SourceNode source
193+
where node = badCrypto(description, source)
194+
select node, "Using " + description + " on a $@ produces biased results.", source,
195+
"cryptographically secure random number"

javascript/ql/src/semmle/javascript/security/dataflow/InsecureRandomness.qll

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,7 @@ module InsecureRandomness {
3636
}
3737

3838
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
39-
// Assume that all operations on tainted values preserve taint: crypto is hard
40-
succ.asExpr().(BinaryExpr).getAnOperand() = pred.asExpr()
41-
or
42-
succ.asExpr().(UnaryExpr).getOperand() = pred.asExpr()
43-
or
44-
exists(DataFlow::MethodCallNode mc |
45-
mc = DataFlow::globalVarRef("Math").getAMemberCall(_) and
46-
pred = mc.getAnArgument() and
47-
succ = mc
48-
)
39+
InsecureRandomness::isAdditionalTaintStep(pred, succ)
4940
}
5041
}
5142
}

javascript/ql/src/semmle/javascript/security/dataflow/InsecureRandomnessCustomizations.qll

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,20 @@ module InsecureRandomness {
7878
class CryptoKeySink extends Sink {
7979
CryptoKeySink() { this instanceof CryptographicKey }
8080
}
81+
82+
/**
83+
* Holds if the step `pred` -> `succ` is an additional taint-step for random values that are not cryptographically secure.
84+
*/
85+
predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
86+
// Assume that all operations on tainted values preserve taint: crypto is hard
87+
succ.asExpr().(BinaryExpr).getAnOperand() = pred.asExpr()
88+
or
89+
succ.asExpr().(UnaryExpr).getOperand() = pred.asExpr()
90+
or
91+
exists(DataFlow::MethodCallNode mc |
92+
mc = DataFlow::globalVarRef("Math").getAMemberCall(_) and
93+
pred = mc.getAnArgument() and
94+
succ = mc
95+
)
96+
}
8197
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
| bad-random.js:3:11:3:61 | crypto. ... s(1)[0] | Using addition on a $@ produces biased results. | bad-random.js:3:11:3:31 | crypto. ... ytes(1) | cryptographically secure random number |
2+
| bad-random.js:3:11:3:61 | crypto. ... s(1)[0] | Using addition on a $@ produces biased results. | bad-random.js:3:38:3:58 | crypto. ... ytes(1) | cryptographically secure random number |
3+
| bad-random.js:4:11:4:61 | crypto. ... s(1)[0] | Using multiplication on a $@ produces biased results. | bad-random.js:4:11:4:31 | crypto. ... ytes(1) | cryptographically secure random number |
4+
| bad-random.js:4:11:4:61 | crypto. ... s(1)[0] | Using multiplication on a $@ produces biased results. | bad-random.js:4:38:4:58 | crypto. ... ytes(1) | cryptographically secure random number |
5+
| bad-random.js:9:28:9:43 | buffer[i] / 25.6 | Using division and rounding the result on a $@ produces biased results. | bad-random.js:6:16:6:40 | crypto. ... (bytes) | cryptographically secure random number |
6+
| bad-random.js:11:17:11:31 | buffer[i] % 100 | Using modulo on a $@ produces biased results. | bad-random.js:6:16:6:40 | crypto. ... (bytes) | cryptographically secure random number |
7+
| bad-random.js:14:11:14:63 | Number( ... (0, 3)) | Using string concatenation on a $@ produces biased results. | bad-random.js:14:25:14:45 | crypto. ... ytes(3) | cryptographically secure random number |
8+
| bad-random.js:73:32:73:42 | byte / 25.6 | Using division and rounding the result on a $@ produces biased results. | bad-random.js:70:20:70:44 | crypto. ... (bytes) | cryptographically secure random number |
9+
| bad-random.js:75:21:75:30 | byte % 100 | Using modulo on a $@ produces biased results. | bad-random.js:70:20:70:44 | crypto. ... (bytes) | cryptographically secure random number |
10+
| bad-random.js:81:11:81:51 | secureR ... (10)[0] | Using addition on a $@ produces biased results. | bad-random.js:81:11:81:26 | secureRandom(10) | cryptographically secure random number |
11+
| bad-random.js:81:11:81:51 | secureR ... (10)[0] | Using addition on a $@ produces biased results. | bad-random.js:81:33:81:48 | secureRandom(10) | cryptographically secure random number |
12+
| bad-random.js:85:11:85:35 | goodRan ... Random2 | Using addition on a $@ produces biased results. | bad-random.js:83:23:83:38 | secureRandom(10) | cryptographically secure random number |
13+
| bad-random.js:85:11:85:35 | goodRan ... Random2 | Using addition on a $@ produces biased results. | bad-random.js:84:23:84:38 | secureRandom(10) | cryptographically secure random number |
14+
| bad-random.js:87:16:87:24 | bad + bad | Using addition on a $@ produces biased results. | bad-random.js:83:23:83:38 | secureRandom(10) | cryptographically secure random number |
15+
| bad-random.js:87:16:87:24 | bad + bad | Using addition on a $@ produces biased results. | bad-random.js:84:23:84:38 | secureRandom(10) | cryptographically secure random number |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-327/BadRandomness.ql

0 commit comments

Comments
 (0)