Skip to content

Commit 4d6521f

Browse files
authored
Merge pull request #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
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import java
2+
import semmle.code.java.dataflow.DataFlow
3+
import semmle.code.java.security.InsecureRandomnessQuery
4+
import TestUtilities.InlineExpectationsTest
5+
6+
module WeakRandomTest implements TestSig {
7+
string getARelevantTag() { result = "hasWeakRandomFlow" }
8+
9+
predicate hasActualResult(Location location, string element, string tag, string value) {
10+
tag = "hasWeakRandomFlow" and
11+
exists(DataFlow::Node sink | InsecureRandomnessFlow::flowTo(sink) |
12+
sink.getLocation() = location and
13+
element = sink.toString() and
14+
value = ""
15+
)
16+
}
17+
}
18+
19+
import MakeTest<WeakRandomTest>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import java.io.IOException;
2+
import java.util.Random;
3+
import java.util.concurrent.ThreadLocalRandom;
4+
import java.security.SecureRandom;
5+
import javax.servlet.ServletException;
6+
import javax.servlet.http.HttpServlet;
7+
import javax.servlet.http.HttpServletRequest;
8+
import javax.servlet.http.HttpServletResponse;
9+
import javax.servlet.http.Cookie;
10+
import org.apache.commons.lang3.RandomStringUtils;
11+
import org.owasp.esapi.Encoder;
12+
13+
public class WeakRandomCookies extends HttpServlet {
14+
HttpServletResponse response;
15+
16+
public void doGet() {
17+
Random r = new Random();
18+
19+
int c = r.nextInt();
20+
// BAD: The cookie value may be predictable.
21+
Cookie cookie = new Cookie("name", Integer.toString(c)); // $hasWeakRandomFlow
22+
23+
Encoder enc = null;
24+
int c2 = r.nextInt();
25+
String value = enc.encodeForHTML(Integer.toString(c2));
26+
// BAD: The cookie value may be predictable.
27+
Cookie cookie2 = new Cookie("name", value); // $hasWeakRandomFlow
28+
29+
byte[] bytes = new byte[16];
30+
r.nextBytes(bytes);
31+
// BAD: The cookie value may be predictable.
32+
Cookie cookie3 = new Cookie("name", new String(bytes)); // $hasWeakRandomFlow
33+
34+
SecureRandom sr = new SecureRandom();
35+
36+
byte[] bytes2 = new byte[16];
37+
sr.nextBytes(bytes2);
38+
// GOOD: The cookie value is unpredictable.
39+
Cookie cookie4 = new Cookie("name", new String(bytes2));
40+
41+
ThreadLocalRandom tlr = ThreadLocalRandom.current();
42+
43+
Cookie cookie5 = new Cookie("name", Integer.toString(tlr.nextInt())); // $hasWeakRandomFlow
44+
45+
Cookie cookie6 = new Cookie("name", RandomStringUtils.random(10)); // $hasWeakRandomFlow
46+
47+
Cookie cookie7 = new Cookie("name", RandomStringUtils.randomAscii(10)); // $hasWeakRandomFlow
48+
49+
long c3 = r.nextLong();
50+
// BAD: The cookie value may be predictable.
51+
Cookie cookie8 = new Cookie("name", Long.toString(c3 * 5)); // $hasWeakRandomFlow
52+
53+
double c4 = Math.random();
54+
// BAD: The cookie value may be predictable.
55+
Cookie cookie9 = new Cookie("name", Double.toString(c4)); // $hasWeakRandomFlow
56+
57+
double c5 = Math.random();
58+
// BAD: The cookie value may be predictable.
59+
Cookie cookie10 = new Cookie("name", Double.toString(++c5)); // $hasWeakRandomFlow
60+
}
61+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/apache-commons-lang3-3.7:${testdir}/../../../stubs/esapi-2.0.1

java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)