Skip to content

Commit 696d19c

Browse files
authored
Merge pull request github#3773 from erik-krogh/guardedCrypto
Approved by asgerf
2 parents 3982da5 + 76ed03f commit 696d19c

File tree

4 files changed

+76
-48
lines changed

4 files changed

+76
-48
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
| Expression has no effect (`js/useless-expression`) | Fewer results | This query no longer flags an expression when that expression is the only content of the containing file. |
5757
| Hard-coded credentials (`js/hardcoded-credentials`) | More results | This query now recognizes hard-coded credentials sent via HTTP authorization headers. |
5858
| Incomplete URL scheme check (`js/incomplete-url-scheme-check`) | More results | This query now recognizes additional url scheme checks. |
59+
| Insecure randomness (`js/insecure-randomness`) | Fewer results | This query now recognizes when an insecure random value is used as a fallback when secure random values are unsupported. |
5960
| Misspelled variable name (`js/misspelled-variable-name`) | Message changed | The message for this query now correctly identifies the misspelled variable in additional cases. |
6061
| Non-linear pattern (`js/non-linear-pattern`) | Fewer duplicates and message changed | This query now generates fewer duplicate alerts and has a clearer explanation in case of type annotations used in a pattern. |
6162
| Prototype pollution in utility function (`js/prototype-pollution-utility`) | More results | This query now recognizes additional utility functions as vulnerable to prototype polution. |

javascript/ql/src/Security/CWE-327/BadRandomness.ql

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,6 @@ private DataFlow::Node isPowerOfTwoMinusOne() {
5555
)
5656
}
5757

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-
7858
/**
7959
* Gets the pseudo-property used to track elements inside a Buffer.
8060
* The API for `Set` is close enough to the API for `Buffer` that we can reuse the type-tracking steps.
@@ -86,7 +66,7 @@ private string prop() { result = DataFlow::PseudoProperties::setElement() }
8666
*/
8767
private DataFlow::Node goodRandom(DataFlow::TypeTracker t, DataFlow::SourceNode source) {
8868
t.startInProp(prop()) and
89-
result = randomBufferSource() and
69+
result = InsecureRandomness::randomBufferSource() and
9070
result = source
9171
or
9272
// Loading a number from a `Buffer`.

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

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,39 +30,50 @@ module InsecureRandomness {
3030
override InvokeExpr astNode;
3131

3232
DefaultSource() {
33-
exists(DataFlow::ModuleImportNode mod, string name | mod.getPath() = name |
34-
// require("random-number")();
35-
name = "random-number" and
36-
this = mod.getACall()
33+
not this.getContainer() = getASecureRandomGeneratingFunction() and
34+
(
35+
exists(DataFlow::ModuleImportNode mod, string name | mod.getPath() = name |
36+
// require("random-number")();
37+
name = "random-number" and
38+
this = mod.getACall()
39+
or
40+
// require("random-int")();
41+
name = "random-int" and
42+
this = mod.getACall()
43+
or
44+
// require("random-float")();
45+
name = "random-float" and
46+
this = mod.getACall()
47+
or
48+
// require('random-seed').create()();
49+
name = "random-seed" and
50+
this = mod.getAMemberCall("create").getACall()
51+
or
52+
// require('unique-random')()();
53+
name = "unique-random" and
54+
this = mod.getACall().getACall()
55+
)
3756
or
38-
// require("random-int")();
39-
name = "random-int" and
40-
this = mod.getACall()
57+
// Math.random()
58+
this = DataFlow::globalVarRef("Math").getAMemberCall("random")
4159
or
42-
// require("random-float")();
43-
name = "random-float" and
44-
this = mod.getACall()
60+
// (new require('chance')).<name>()
61+
this = DataFlow::moduleImport("chance").getAnInstantiation().getAMemberInvocation(_)
4562
or
46-
// require('random-seed').create()();
47-
name = "random-seed" and
48-
this = mod.getAMemberCall("create").getACall()
49-
or
50-
// require('unique-random')()();
51-
name = "unique-random" and
52-
this = mod.getACall().getACall()
63+
// require('crypto').pseudoRandomBytes()
64+
this = DataFlow::moduleMember("crypto", "pseudoRandomBytes").getAnInvocation()
5365
)
54-
or
55-
// Math.random()
56-
this = DataFlow::globalVarRef("Math").getAMemberCall("random")
57-
or
58-
// (new require('chance')).<name>()
59-
this = DataFlow::moduleImport("chance").getAnInstantiation().getAMemberInvocation(_)
60-
or
61-
// require('crypto').pseudoRandomBytes()
62-
this = DataFlow::moduleMember("crypto", "pseudoRandomBytes").getAnInvocation()
6366
}
6467
}
6568

69+
/**
70+
* Gets a container that at some point generates a secure random value.
71+
*/
72+
pragma[noinline]
73+
private StmtContainer getASecureRandomGeneratingFunction() {
74+
result = randomBufferSource().getContainer()
75+
}
76+
6677
/**
6778
* A sensitive write, considered as a sink for random values that are not cryptographically
6879
* secure.
@@ -94,4 +105,24 @@ module InsecureRandomness {
94105
succ = mc
95106
)
96107
}
108+
109+
/**
110+
* Gets a Buffer/TypedArray containing cryptographically secure random numbers.
111+
*/
112+
DataFlow::SourceNode randomBufferSource() {
113+
result = DataFlow::moduleMember("crypto", ["randomBytes", "randomFillSync"]).getACall()
114+
or
115+
exists(DataFlow::CallNode call |
116+
call = DataFlow::moduleMember("crypto", ["randomFill", "randomFillSync"]) and
117+
result = call.getArgument(0).getALocalSource()
118+
)
119+
or
120+
result = DataFlow::globalVarRef("crypto").getAMethodCall("getRandomValues")
121+
or
122+
result = DataFlow::moduleImport("secure-random").getACall()
123+
or
124+
result =
125+
DataFlow::moduleImport("secure-random")
126+
.getAMethodCall(["randomArray", "randomUint8Array", "randomBuffer"])
127+
}
97128
}

javascript/ql/test/query-tests/Security/CWE-338/tst.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,20 @@ function f18() {
9393
(function(){
9494
var crypto = require('crypto');
9595
crypto.createHmac('sha256', Math.random());
96-
})()
96+
})();
97+
98+
(function () {
99+
function genRandom() {
100+
if (window.crypto && crypto.getRandomValues && !isSafari()) {
101+
var a = window.crypto.getRandomValues(new Uint32Array(3)),
102+
token = '';
103+
for (var i = 0, l = a.length; i < l; i++) {
104+
token += a[i].toString(36);
105+
}
106+
return token;
107+
} else {
108+
return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
109+
}
110+
};
111+
var secret = genRandom(); // OK - Math.random() is only a fallback.
112+
})();

0 commit comments

Comments
 (0)