Skip to content

Commit 4d6521f

Browse files
authored
Merge pull request github#13608 from egregius313/egregius313/weak-randomness
Java: Add Weak Randomness Query (CWE-330/338)
2 parents 3dea467 + 06eef93 commit 4d6521f

File tree

15 files changed

+329
-3
lines changed

15 files changed

+329
-3
lines changed

java/ql/lib/ext/java.security.spec.model.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ extensions:
33
pack: codeql/java-all
44
extensible: sinkModel
55
data:
6+
- ["java.security.spec", "DSAPrivateKeySpec", False, "DSAPrivateKeySpec", "", "", "Argument[0..3]", "credentials-key", "manual"]
7+
- ["java.security.spec", "ECPrivateKeySpec", False, "ECPrivateKeySpec", "", "", "Argument[0]", "credentials-key", "manual"]
68
- ["java.security.spec", "EncodedKeySpec", False, "EncodedKeySpec", "(byte[])", "", "Argument[0]", "credentials-key", "hq-generated"]
79
- ["java.security.spec", "PKCS8EncodedKeySpec", False, "PKCS8EncodedKeySpec", "(byte[])", "", "Argument[0]", "credentials-key", "hq-generated"]
10+
- ["java.security.spec", "RSAMultiPrimePrivateCrtKeySpec", False, "RSAMultiPrimePrivateCrtKeySpec", "", "", "Argument[2]", "credentials-key", "manual"]
11+
- ["java.security.spec", "RSAPrivateCrtKeySpec", False, "RSAPrivateCrtKeySpec", "", "", "Argument[2]", "credentials-key", "manual"]
12+
- ["java.security.spec", "RSAPrivateKeySpec", False, "RSAPrivateKeySpec", "", "", "Argument[1]", "credentials-key", "manual"]
813
- ["java.security.spec", "X509EncodedKeySpec", False, "X509EncodedKeySpec", "(byte[])", "", "Argument[0]", "credentials-key", "hq-generated"]

java/ql/lib/ext/javax.crypto.spec.model.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ extensions:
1111
pack: codeql/java-all
1212
extensible: sinkModel
1313
data:
14-
- ["javax.crypto.spec", "PBEKeySpec", False, "PBEKeySpec", "(char[])", "", "Argument[0]", "credentials-password", "hq-generated"]
15-
- ["javax.crypto.spec", "PBEKeySpec", False, "PBEKeySpec", "(char[],byte[],int)", "", "Argument[0]", "credentials-password", "hq-generated"]
16-
- ["javax.crypto.spec", "PBEKeySpec", False, "PBEKeySpec", "(char[],byte[],int,int)", "", "Argument[0]", "credentials-password", "hq-generated"]
1714
- ["javax.crypto.spec", "DESKeySpec", False, "DESKeySpec", "(byte[])", "", "Argument[0]", "credentials-key", "hq-generated"]
1815
- ["javax.crypto.spec", "DESKeySpec", False, "DESKeySpec", "(byte[],int)", "", "Argument[0]", "credentials-key", "hq-generated"]
1916
- ["javax.crypto.spec", "DESKeySpec", False, "isParityAdjusted", "(byte[],int)", "", "Argument[0]", "credentials-key", "hq-generated"]
2017
- ["javax.crypto.spec", "DESKeySpec", False, "isWeak", "(byte[],int)", "", "Argument[0]", "credentials-key", "hq-generated"]
2118
- ["javax.crypto.spec", "DESedeKeySpec", False, "DESedeKeySpec", "(byte[])", "", "Argument[0]", "credentials-key", "hq-generated"]
2219
- ["javax.crypto.spec", "DESedeKeySpec", False, "DESedeKeySpec", "(byte[],int)", "", "Argument[0]", "credentials-key", "hq-generated"]
2320
- ["javax.crypto.spec", "DESedeKeySpec", False, "isParityAdjusted", "(byte[],int)", "", "Argument[0]", "credentials-key", "hq-generated"]
21+
- ["javax.crypto.spec", "DHPrivateKeySpec", False, "DHPrivateKeySpec", "", "", "Argument[0]", "credentials-key", "manual"]
22+
- ["javax.crypto.spec", "PBEKeySpec", False, "PBEKeySpec", "", "", "Argument[1]", "encryption-salt", "manual"]
23+
- ["javax.crypto.spec", "PBEKeySpec", False, "PBEKeySpec", "(char[])", "", "Argument[0]", "credentials-password", "hq-generated"]
24+
- ["javax.crypto.spec", "PBEKeySpec", False, "PBEKeySpec", "(char[],byte[],int)", "", "Argument[0]", "credentials-password", "hq-generated"]
25+
- ["javax.crypto.spec", "PBEKeySpec", False, "PBEKeySpec", "(char[],byte[],int,int)", "", "Argument[0]", "credentials-password", "hq-generated"]
26+
- ["javax.crypto.spec", "PBEParameterSpec", False, "PBEParameterSpec", "", "", "Argument[0]", "encryption-salt", "manual"]
2427
- ["javax.crypto.spec", "SecretKeySpec", False, "SecretKeySpec", "(byte[],String)", "", "Argument[0]", "credentials-key", "hq-generated"]
2528
- ["javax.crypto.spec", "SecretKeySpec", False, "SecretKeySpec", "(byte[],int,int,String)", "", "Argument[0]", "credentials-key", "hq-generated"]
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/** Provides classes and predicates for reasoning about insecure randomness. */
2+
3+
import java
4+
private import semmle.code.java.frameworks.Servlets
5+
private import semmle.code.java.security.SensitiveActions
6+
private import semmle.code.java.security.SensitiveApi
7+
private import semmle.code.java.dataflow.TaintTracking
8+
private import semmle.code.java.dataflow.ExternalFlow
9+
private import semmle.code.java.security.RandomQuery
10+
11+
/**
12+
* A node representing a source of insecure randomness.
13+
*
14+
* For example, use of `java.util.Random` or `java.lang.Math.random`.
15+
*/
16+
abstract class InsecureRandomnessSource extends DataFlow::Node { }
17+
18+
private class RandomMethodSource extends InsecureRandomnessSource {
19+
RandomMethodSource() {
20+
exists(RandomDataSource s | this.asExpr() = s.getOutput() |
21+
not s.getQualifier().getType() instanceof SafeRandomImplementation
22+
)
23+
}
24+
}
25+
26+
/**
27+
* A type which is an implementation of `java.util.Random` but considered to be safe.
28+
*
29+
* For example, `java.security.SecureRandom`.
30+
*/
31+
abstract private class SafeRandomImplementation extends RefType { }
32+
33+
private class TypeSecureRandom extends SafeRandomImplementation instanceof SecureRandomNumberGenerator
34+
{ }
35+
36+
private class TypeHadoopOsSecureRandom extends SafeRandomImplementation {
37+
TypeHadoopOsSecureRandom() {
38+
this.hasQualifiedName("org.apache.hadoop.crypto.random", "OsSecureRandom")
39+
}
40+
}
41+
42+
/**
43+
* A node representing an operation which should not use a Insecurely random value.
44+
*/
45+
abstract class InsecureRandomnessSink extends DataFlow::Node { }
46+
47+
/**
48+
* A node which sets the value of a cookie.
49+
*/
50+
private class CookieSink extends InsecureRandomnessSink {
51+
CookieSink() {
52+
exists(Call c |
53+
c.(ClassInstanceExpr).getConstructedType() instanceof TypeCookie and
54+
this.asExpr() = c.getArgument(1)
55+
or
56+
c.(MethodCall).getMethod().getDeclaringType() instanceof TypeCookie and
57+
c.(MethodCall).getMethod().hasName("setValue") and
58+
this.asExpr() = c.getArgument(0)
59+
)
60+
}
61+
}
62+
63+
private class SensitiveActionSink extends InsecureRandomnessSink {
64+
SensitiveActionSink() { this.asExpr() instanceof SensitiveExpr }
65+
}
66+
67+
private class CredentialsSink extends InsecureRandomnessSink instanceof CredentialsSinkNode { }
68+
69+
/**
70+
* A taint-tracking configuration for Insecure randomness.
71+
*/
72+
module InsecureRandomnessConfig implements DataFlow::ConfigSig {
73+
predicate isSource(DataFlow::Node src) { src instanceof InsecureRandomnessSource }
74+
75+
predicate isSink(DataFlow::Node sink) { sink instanceof InsecureRandomnessSink }
76+
77+
predicate isBarrierIn(DataFlow::Node n) { isSource(n) }
78+
79+
predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
80+
n1.asExpr() = n2.asExpr().(BinaryExpr).getAnOperand()
81+
or
82+
n1.asExpr() = n2.asExpr().(UnaryExpr).getExpr()
83+
or
84+
exists(MethodCall mc, string methodName |
85+
mc.getMethod().hasQualifiedName("org.owasp.esapi", "Encoder", methodName) and
86+
methodName.matches("encode%")
87+
|
88+
n1.asExpr() = mc.getArgument(0) and
89+
n2.asExpr() = mc
90+
)
91+
}
92+
}
93+
94+
/**
95+
* Taint-tracking flow of a Insecurely random value into a sensitive sink.
96+
*/
97+
module InsecureRandomnessFlow = TaintTracking::Global<InsecureRandomnessConfig>;

java/ql/lib/semmle/code/java/security/RandomDataSource.qll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ class StdlibRandomSource extends RandomDataSource {
107107
}
108108
}
109109

110+
/**
111+
* A method access calling the `random` of `java.lang.Math`.
112+
*/
113+
class MathRandomSource extends RandomDataSource {
114+
MathRandomSource() { this.getMethod().hasQualifiedName("java.lang", "Math", "random") }
115+
116+
override Expr getOutput() { result = this }
117+
}
118+
110119
/**
111120
* A method access calling a method declared on `org.apache.commons.lang3.RandomUtils`
112121
* that returns random data or writes random data to an argument.
@@ -143,3 +152,19 @@ class ApacheCommonsRandomSource extends RandomDataSource {
143152

144153
override Expr getOutput() { result = this }
145154
}
155+
156+
/**
157+
* A method access calling a method declared on `org.apache.commons.lang3.RandomStringUtils`
158+
*/
159+
class ApacheCommonsRandomStringSource extends RandomDataSource {
160+
ApacheCommonsRandomStringSource() {
161+
exists(Method m | m = this.getMethod() |
162+
m.getName().matches("random%") and
163+
m.getDeclaringType()
164+
.hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"],
165+
"RandomStringUtils")
166+
)
167+
}
168+
169+
override Expr getOutput() { result = this }
170+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
If you use a cryptographically weak pseudo-random number generator to generate security-sensitive values,
8+
such as passwords, attackers can more easily predict those values.
9+
</p>
10+
<p>
11+
Pseudo-random number generators generate a sequence of numbers that only approximates the properties
12+
of random numbers. The sequence is not truly random because it is completely determined by a
13+
relatively small set of initial values (the seed). If the random number generator is
14+
cryptographically weak, then this sequence may be easily predictable through outside observations.
15+
</p>
16+
17+
</overview>
18+
<recommendation>
19+
<p>
20+
The <code>java.util.Random</code> random number generator is not cryptographically secure. Use a secure random number generator such as <code>java.security.SecureRandom</code> instead.
21+
</p>
22+
<p>
23+
Use a cryptographically secure pseudo-random number generator if the output is to be used in a
24+
security-sensitive context. As a general rule, a value should be considered "security-sensitive"
25+
if predicting it would allow the attacker to perform an action that they would otherwise be unable
26+
to perform. For example, if an attacker could predict the random password generated for a new user,
27+
they would be able to log in as that new user.
28+
</p>
29+
</recommendation>
30+
31+
<example>
32+
33+
<p>
34+
The following examples show different ways of generating a cookie with a random value.
35+
</p>
36+
37+
<p>
38+
In the first (BAD) case, we generate a fresh cookie by appending a random integer to the end of a static
39+
string. The random number generator used (<code>Random</code>) is not cryptographically secure,
40+
so it may be possible for an attacker to predict the generated cookie.
41+
</p>
42+
43+
<sample src="examples/InsecureRandomnessCookie.java" />
44+
45+
<p>
46+
In the second (GOOD) case, we generate a fresh cookie by appending a random integer to the end of a static
47+
string. The random number generator used (<code>SecureRandom</code>) is cryptographically secure,
48+
so it is not possible for an attacker to predict the generated cookie.
49+
</p>
50+
51+
<sample src="examples/SecureRandomnessCookie.java" />
52+
53+
</example>
54+
55+
<references>
56+
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Pseudorandom_number_generator">Pseudo-random number generator</a>.</li>
57+
<li>
58+
Java Docs: <a href="http://docs.oracle.com/javase/8/docs/api/java/util/Random.html">Random</a>.
59+
</li>
60+
<li>
61+
Java Docs: <a href="http://docs.oracle.com/javase/8/docs/api/java/security/SecureRandom.html">SecureRandom</a>.
62+
</li>
63+
</references>
64+
</qhelp>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @name Insecure randomness
3+
* @description Using a cryptographically Insecure pseudo-random number generator to generate a
4+
* security-sensitive value may allow an attacker to predict what value will
5+
* be generated.
6+
* @kind path-problem
7+
* @problem.severity warning
8+
* @security-severity 7.8
9+
* @precision high
10+
* @id java/insecure-randomness
11+
* @tags security
12+
* external/cwe/cwe-330
13+
* external/cwe/cwe-338
14+
*/
15+
16+
import java
17+
import semmle.code.java.security.InsecureRandomnessQuery
18+
import InsecureRandomnessFlow::PathGraph
19+
20+
from InsecureRandomnessFlow::PathNode source, InsecureRandomnessFlow::PathNode sink
21+
where InsecureRandomnessFlow::flowPath(source, sink)
22+
select sink.getNode(), source, sink, "Potential Insecure randomness due to a $@.", source.getNode(),
23+
"Insecure randomness source."
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Random r = new Random();
2+
3+
byte[] bytes = new byte[16];
4+
r.nextBytes(bytes);
5+
6+
String cookieValue = encode(bytes);
7+
8+
Cookie cookie = new Cookie("name", cookieValue);
9+
response.addCookie(cookie);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
SecureRandom r = new SecureRandom();
2+
3+
byte[] bytes = new byte[16];
4+
r.nextBytes(bytes);
5+
6+
String cookieValue = encode(bytes);
7+
8+
Cookie cookie = new Cookie("name", cookieValue);
9+
response.addCookie(cookie);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: newQuery
3+
---
4+
* Added the `java/insecure-randomness` query to detect uses of weakly random values which an attacker may be able to predict. Also added the `crypto-parameter` sink kind for sinks which represent the parameters and keys of cryptographic operations.
5+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
failures
2+
testFailures

0 commit comments

Comments
 (0)