Skip to content

Commit eb10982

Browse files
authored
Merge pull request github#7252 from museljh/feature/cwe-338
Python: CWE-338 insecureRandomness
2 parents 9217d0e + 1dd15fa commit eb10982

File tree

9 files changed

+212
-0
lines changed

9 files changed

+212
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Using a cryptographically weak pseudo-random number generator to generate a security-sensitive value,
8+
such as a password, makes it easier for an attacker to predict the value.
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+
</overview>
17+
<recommendation>
18+
<p>
19+
Use a cryptographically secure pseudo-random number generator if the output is to be used in a
20+
security sensitive context. As a rule of thumb, a value should be considered "security sensitive"
21+
if predicting it would allow the attacker to perform an action that they would otherwise be unable
22+
to perform. For example, if an attacker could predict the random password generated for a new user,
23+
they would be able to log in as that new user.
24+
</p>
25+
<p>
26+
For Python, <code>secrets</code> provides a cryptographically secure pseudo-random
27+
number generator. <code>random</code> is not cryptographically secure, and should be avoided in
28+
security contexts.
29+
</p>
30+
</recommendation>
31+
32+
<example>
33+
<p>
34+
The example below uses the <code>random</code> package instead of <code>secrets</code> to generate a password:
35+
</p>
36+
<sample src="examples/InsecureRandomness.py" />
37+
<p>
38+
Instead, use <code>secrets</code>:
39+
</p>
40+
<sample src="examples/InsecureRandomnessGood.py" />
41+
</example>
42+
43+
<references>
44+
<li>Wikipedia. <a href="http://en.wikipedia.org/wiki/Pseudorandom_number_generator">Pseudo-random number generator</a>.</li>
45+
<li>OWASP: <a href="https://owasp.org/www-community/vulnerabilities/Insecure_Randomness">Insecure Randomness</a>.</li>
46+
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation">Secure Random Number Generation</a>.</li>
47+
</references>
48+
</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 weak 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 py/insecure-randomness
11+
* @tags security
12+
* external/cwe/cwe-338
13+
*/
14+
15+
import python
16+
import experimental.semmle.python.security.InsecureRandomness::InsecureRandomness
17+
import semmle.python.dataflow.new.DataFlow
18+
import DataFlow::PathGraph
19+
20+
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
21+
where cfg.hasFlowPath(source, sink)
22+
select sink.getNode(), source, sink, "Cryptographically insecure $@ in a security context.",
23+
source.getNode(), "random value"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import random
2+
3+
4+
def generatePassword():
5+
# BAD: the random is not cryptographically secure
6+
return random.random()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import secrets
2+
3+
4+
def generatePassword():
5+
# GOOD: the random is cryptographically secure
6+
secret_generator = secrets.SystemRandom()
7+
return secret_generator.random()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Provides a taint tracking configuration for reasoning about random
3+
* values that are not cryptographically secure.
4+
*
5+
* Note, for performance reasons: only import this file if
6+
* `InsecureRandomness::Configuration` is needed, otherwise
7+
* `InsecureRandomnessCustomizations` should be imported instead.
8+
*/
9+
10+
private import python
11+
import semmle.python.dataflow.new.DataFlow
12+
import semmle.python.dataflow.new.TaintTracking
13+
14+
/**
15+
* A taint tracking configuration for random values that are not cryptographically secure.
16+
*/
17+
module InsecureRandomness {
18+
import InsecureRandomnessCustomizations::InsecureRandomness
19+
20+
/**
21+
* A taint-tracking configuration for reasoning about random values that are
22+
* not cryptographically secure.
23+
*/
24+
class Configuration extends TaintTracking::Configuration {
25+
Configuration() { this = "InsecureRandomness" }
26+
27+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
28+
29+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
30+
31+
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
32+
33+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
34+
guard instanceof SanitizerGuard
35+
}
36+
}
37+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for reasoning about random values that are
3+
* not cryptographically secure, as well as extension points for adding your own.
4+
*/
5+
6+
private import python
7+
private import semmle.python.ApiGraphs
8+
private import semmle.python.Concepts
9+
private import semmle.python.dataflow.new.BarrierGuards
10+
private import semmle.python.dataflow.new.internal.DataFlowPrivate
11+
12+
/**
13+
* Provides default sources, sinks and sanitizers for reasoning about random values that are
14+
* not cryptographically secure, as well as extension points for adding your own.
15+
*/
16+
module InsecureRandomness {
17+
/**
18+
* A data flow source for random values that are not cryptographically secure.
19+
*/
20+
abstract class Source extends DataFlow::Node { }
21+
22+
/**
23+
* A data flow sink for random values that are not cryptographically secure.
24+
*/
25+
abstract class Sink extends DataFlow::Node { }
26+
27+
/**
28+
* A sanitizer for random values that are not cryptographically secure.
29+
*/
30+
abstract class Sanitizer extends DataFlow::Node { }
31+
32+
/**
33+
* A sanitizer guard for random values that are not cryptographically secure.
34+
*/
35+
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
36+
37+
/**
38+
* A random source that is not sufficient for security use. So far this is only made up
39+
* of the math package's rand function, more insufficient random sources can be added here.
40+
*/
41+
class InsecureRandomSource extends Source {
42+
InsecureRandomSource() {
43+
this =
44+
API::moduleImport("random")
45+
.getMember([
46+
"betavariate", "choice", "choices", "expovariate", "gammavariate", "gauss",
47+
"getrandbits", "getstate", "lognormvariate", "normalvariate", "paretovariate",
48+
"randbytes", "randint", "random", "randrange", "sample", "seed", "setstate",
49+
"shuffle", "triangular", "uniform", "vonmisesvariate", "weibullvariate"
50+
])
51+
.getACall()
52+
}
53+
}
54+
55+
/**
56+
* A use in a function that heuristically deals with unsafe random numbers or random strings.
57+
*/
58+
class RandomFnSink extends Sink {
59+
RandomFnSink() {
60+
exists(DataFlowCallable randomFn |
61+
randomFn
62+
.getName()
63+
.regexpMatch("(?i).*(gen(erate)?|make|mk|create).*(nonce|salt|pepper|Password).*")
64+
|
65+
this.getEnclosingCallable() = randomFn
66+
)
67+
}
68+
}
69+
70+
/**
71+
* A cryptographic key, considered as a sink for random values that are not cryptographically
72+
* secure.
73+
*/
74+
class CryptoKeySink extends Sink {
75+
CryptoKeySink() {
76+
exists(Cryptography::CryptographicOperation operation | this = operation.getAnInput())
77+
}
78+
}
79+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
edges
2+
nodes
3+
| InsecureRandomness.py:5:12:5:26 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
4+
subpaths
5+
#select
6+
| InsecureRandomness.py:5:12:5:26 | ControlFlowNode for Attribute() | InsecureRandomness.py:5:12:5:26 | ControlFlowNode for Attribute() | InsecureRandomness.py:5:12:5:26 | ControlFlowNode for Attribute() | Cryptographically insecure $@ in a security context. | InsecureRandomness.py:5:12:5:26 | ControlFlowNode for Attribute() | random value |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import random
2+
3+
def generatePassword():
4+
# BAD: the random is not cryptographically secure
5+
return random.random()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE-338/InsecureRandomness.ql

0 commit comments

Comments
 (0)