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

Skip to content

Commit 111f6d4

Browse files
committed
introduce query to detect biased random number generators
1 parent c580ada commit 111f6d4

5 files changed

Lines changed: 249 additions & 0 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: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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.InferredTypes
15+
private import semmle.javascript.dataflow.internal.StepSummary
16+
17+
/**
18+
* Gets a Buffer/TypedArray containing cryptographically secure random numbers.
19+
*/
20+
private DataFlow::SourceNode randomBufferSource() {
21+
result = DataFlow::moduleMember("crypto", ["randomBytes", "randomFillSync"]).getACall()
22+
or
23+
exists(DataFlow::CallNode call |
24+
call = DataFlow::moduleMember("crypto", ["randomFill", "randomFillSync"]) and
25+
result = call.getArgument(0).getALocalSource()
26+
)
27+
or
28+
result = DataFlow::globalVarRef("crypto").getAMethodCall("getRandomValues")
29+
}
30+
31+
/**
32+
* Gets the pseudo-property used to track elements inside a Buffer.
33+
* The API for `Set` is close enough to the API for `Buffer` that we can reuse the type-tracking steps.
34+
*/
35+
private string prop() { result = DataFlow::PseudoProperties::setElement() }
36+
37+
/**
38+
* Gets a reference to a cryptographically secure random number, type tracked using `t`.
39+
*/
40+
private DataFlow::SourceNode goodRandom(DataFlow::TypeTracker t) {
41+
t.startInProp(prop()) and
42+
result = randomBufferSource()
43+
or
44+
// Loading a number from a `Buffer`.
45+
exists(DataFlow::TypeTracker t2 | t = t2.append(LoadStep(prop())) |
46+
// the random generators return arrays/Buffers of random numbers, we therefore track through an indexed read.
47+
exists(DataFlow::PropRead read |
48+
read.getBase() = goodRandom(t2) and
49+
exists(read.getPropertyNameExpr())
50+
)
51+
or
52+
// reading a number from a Buffer.
53+
exists(DataFlow::MethodCallNode call |
54+
call.getReceiver().getALocalSource() = goodRandom(t.continue()) and
55+
call
56+
.getMethodName()
57+
.regexpMatch("read(BigInt|BigUInt|Double|Float|Int|UInt)(8|16|32|64)?(BE|LE)?")
58+
)
59+
)
60+
or
61+
exists(DataFlow::TypeTracker t2 | result = goodRandom(t2).track(t2, t))
62+
or
63+
// re-using the collection steps for `Set`.
64+
exists(DataFlow::TypeTracker t2 |
65+
result = CollectionsTypeTracking::collectionStep(goodRandom(t2), t, t2)
66+
)
67+
}
68+
69+
/**
70+
* Gets a reference to a cryptographically random number.
71+
*/
72+
DataFlow::SourceNode goodRandom() { result = goodRandom(DataFlow::TypeTracker::end()) }
73+
74+
/**
75+
* Gets a node that that produces a biased result from otherwise cryptographically secure random numbers.
76+
*/
77+
DataFlow::Node badCrypto(string description) {
78+
// addition and multiplication - always bad when both the lhs and rhs are random.
79+
exists(BinaryExpr binop | result.asExpr() = binop |
80+
goodRandom().flowsToExpr(binop.getLeftOperand()) and
81+
goodRandom().flowsToExpr(binop.getRightOperand()) and
82+
(
83+
binop.getOperator() = "+" and description = "addition"
84+
or
85+
binop.getOperator() = "*" and description = "multiplication"
86+
)
87+
)
88+
or
89+
// division - always bad
90+
exists(DivExpr div | result.asExpr() = div |
91+
goodRandom().flowsToExpr(div.getLeftOperand()) and
92+
description = "division"
93+
)
94+
or
95+
// modulo - only bad if not by a power of 2 - and the result is not checked for bias
96+
exists(ModExpr mod, DataFlow::SourceNode random |
97+
result.asExpr() = mod and mod.getOperator() = "%"
98+
|
99+
description = "modulo" and
100+
goodRandom() = random and
101+
random.flowsToExpr(mod.getLeftOperand()) and
102+
not mod.getRightOperand().getIntValue() = [2, 4, 8, 16, 32, 64, 128] and
103+
// not exists a comparison that checks if the result is potentially biased.
104+
not exists(BinaryExpr comparison | comparison.getOperator() = [">", "<", "<=", ">="] |
105+
AccessPath::getAnAliasedSourceNode(random).flowsToExpr(comparison.getAnOperand())
106+
or
107+
exists(DataFlow::PropRead otherRead |
108+
otherRead = random.(DataFlow::PropRead).getBase().getALocalSource().getAPropertyRead() and
109+
not exists(otherRead.getPropertyName()) and
110+
otherRead.flowsToExpr(comparison.getAnOperand())
111+
)
112+
)
113+
)
114+
or
115+
// create a number from a string - always a bad idea.
116+
exists(DataFlow::CallNode number, StringOps::ConcatenationRoot root | result = number |
117+
number = DataFlow::globalVarRef(["Number", "parseInt", "parseFloat"]).getACall() and
118+
root = number.getArgument(0) and
119+
goodRandom().flowsTo(root.getALeaf()) and
120+
exists(root.getALeaf().getStringValue()) and
121+
description = "string concatenation"
122+
)
123+
}
124+
125+
from DataFlow::Node node, string description
126+
where node = badCrypto(description)
127+
select node,
128+
"Using " + description + " on cryptographically random numbers produces biased results."
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
| bad-random.js:3:11:3:61 | crypto. ... s(1)[0] | Using addition on cryptographically random numbers produces biased results. |
2+
| bad-random.js:4:11:4:61 | crypto. ... s(1)[0] | Using multiplication on cryptographically random numbers produces biased results. |
3+
| bad-random.js:9:28:9:43 | buffer[i] / 25.6 | Using division on cryptographically random numbers produces biased results. |
4+
| bad-random.js:11:17:11:31 | buffer[i] % 100 | Using modulo on cryptographically random numbers produces biased results. |
5+
| bad-random.js:14:11:14:63 | Number( ... (0, 3)) | Using string concatenation on cryptographically random numbers produces biased results. |
6+
| bad-random.js:73:32:73:42 | byte / 25.6 | Using division on cryptographically random numbers produces biased results. |
7+
| bad-random.js:75:21:75:30 | byte % 100 | Using modulo on cryptographically random numbers produces biased results. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-327/BadRandomness.ql
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const crypto = require('crypto');
2+
3+
var bad = crypto.randomBytes(1)[0] + crypto.randomBytes(1)[0]; // NOT OK
4+
var bad = crypto.randomBytes(1)[0] * crypto.randomBytes(1)[0]; // NOT OK
5+
6+
const buffer = crypto.randomBytes(bytes);
7+
const digits = [];
8+
for (let i = 0; i < buffer.length; ++i) {
9+
digits.push(Math.floor(buffer[i] / 25.6)); // NOT OK
10+
digits.push(buffer[i] % 8); // OK - 8 is a power of 2, so the result is unbiased.
11+
digits.push(buffer[i] % 100); // NOT OK
12+
}
13+
14+
var bad = Number('0.' + crypto.randomBytes(3).readUIntBE(0, 3)); // NOT OK
15+
var good = Number(10 + crypto.randomBytes(3).readUIntBE(0, 3)); // OK
16+
17+
const internals = {};
18+
exports.randomDigits = function (size) {
19+
const digits = [];
20+
21+
let buffer = internals.random(size * 2);
22+
let pos = 0;
23+
24+
while (digits.length < size) {
25+
if (pos >= buffer.length) {
26+
buffer = internals.random(size * 2);
27+
pos = 0;
28+
}
29+
30+
if (buffer[pos] < 250) {
31+
digits.push(buffer[pos] % 10); // GOOD - protected by a bias-checking comparison.
32+
}
33+
++pos;
34+
}
35+
36+
return digits.join('');
37+
};
38+
39+
internals.random = function (bytes) {
40+
try {
41+
return crypto.randomBytes(bytes);
42+
}
43+
catch (err) {
44+
throw new Error("Failed to make bits.");
45+
}
46+
};
47+
48+
exports.randomDigits2 = function (size) {
49+
const digits = [];
50+
51+
let buffer = crypto.randomBytes(size * 2);
52+
let pos = 0;
53+
54+
while (digits.length < size) {
55+
if (pos >= buffer.length) {
56+
buffer = internals.random(size * 2);
57+
pos = 0;
58+
}
59+
var num = buffer[pos];
60+
if (num < 250) {
61+
digits.push(num % 10); // GOOD - protected by a bias-checking comparison.
62+
}
63+
++pos;
64+
}
65+
66+
return digits.join('');
67+
};
68+
69+
function setSteps() {
70+
const buffer = crypto.randomBytes(bytes);
71+
const digits = [];
72+
for (const byte of buffer.values()) {
73+
digits.push(Math.floor(byte / 25.6)); // NOT OK
74+
digits.push(byte % 8); // OK - 8 is a power of 2, so the result is unbiased.
75+
digits.push(byte % 100); // NOT OK
76+
}
77+
}

0 commit comments

Comments
 (0)