Skip to content

Commit 503cc56

Browse files
authored
Merge pull request github#10943 from bananabr/main
Javascript/Python: Tokens built from predictable UUIDs
2 parents 6d77b34 + 63c71b7 commit 503cc56

File tree

6 files changed

+233
-0
lines changed

6 files changed

+233
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
var uuid = require('uuid');
2+
3+
module.exports = function (app) {
4+
5+
app.use('/login', function (req, res) {
6+
7+
var username = req.body.username;
8+
var password = req.body.password;
9+
10+
if (!username) {
11+
res.status(400);
12+
return;
13+
}
14+
15+
if (!password) {
16+
res.status(400);
17+
return;
18+
}
19+
20+
var newToken = {
21+
userId: user._id,
22+
token: uuid.v1(),
23+
created: new Date(),
24+
};
25+
26+
res.status(200).json({
27+
token: newToken.token
28+
});
29+
});
30+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
GUIDs (often called UUIDs) are widely used in modern web applications.
8+
One common use for UUIDs is the generation of one-time-use tokens.
9+
These can used for password reset, and e-mail confirmation routines, for example.
10+
</p>
11+
<p>
12+
There are five versions of UUIDs defined in RFC 4122.
13+
Out of the five, four are generated in a predictable manner.
14+
This means it is possible for someone to predict future UUIDs based on a sample
15+
generated by the target application.
16+
</p>
17+
<p>
18+
Version four is the only UUID version expected to be randomly generated.
19+
Therefore, for situations where predictable tokens are not desired (e.g. password reset tokens),
20+
all other versions should be avoided.
21+
</p>
22+
</overview>
23+
24+
<recommendation>
25+
<p>When using GUIDs/UUIDs for generating tokens that should not be predictable, use version four.</p>
26+
</recommendation>
27+
28+
<example>
29+
<p>This example shows a UUID v1 being used for a password reset routine.
30+
</p>
31+
32+
<sample src="TokenBuiltFromUUID.js" />
33+
</example>
34+
35+
<references>
36+
<li>UUID <a href="https://datatracker.ietf.org/doc/html/rfc4122">RFC</a>.</li>
37+
<li>Daniel Thatcher <i>In GUID We Trust</i> <a href="https://www.intruder.io/research/in-guid-we-trust">article</a>.</li>
38+
<li>UUID exploitation <a href="https://github.com/intruder-io/guidtool">tool</a>.</li>
39+
</references>
40+
</qhelp>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @name Predictable token
3+
* @description Tokens used for sensitive tasks, such as, password recovery,
4+
* and email confirmation, should not use predictable values.
5+
* @kind path-problem
6+
* @precision medium
7+
* @problem.severity error
8+
* @security-severity 5
9+
* @id py/predictable-token
10+
* @tags security
11+
* external/cwe/cwe-340
12+
*/
13+
14+
import javascript
15+
import DataFlow
16+
import DataFlow::PathGraph
17+
18+
class PredictableResultSource extends DataFlow::Node {
19+
PredictableResultSource() {
20+
exists(API::Node uuidCallRet |
21+
uuidCallRet = API::moduleImport("uuid").getMember(["v1", "v3", "v5"]).getReturn()
22+
|
23+
this = uuidCallRet.asSource()
24+
)
25+
}
26+
}
27+
28+
class TokenAssignmentValueSink extends DataFlow::Node {
29+
TokenAssignmentValueSink() {
30+
exists(string name | name.toLowerCase().matches(["%token", "%code"]) |
31+
exists(PropWrite pw | this = pw.getRhs() | pw.getPropertyName().toLowerCase() = name)
32+
or
33+
exists(AssignExpr ae | this = ae.getRhs().flow() |
34+
ae.getLhs().(VariableAccess).getVariable().getName().toLowerCase() = name
35+
)
36+
)
37+
}
38+
}
39+
40+
class TokenBuiltFromUuidConfig extends TaintTracking::Configuration {
41+
TokenBuiltFromUuidConfig() { this = "TokenBuiltFromUuidConfig" }
42+
43+
override predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
44+
45+
override predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
46+
}
47+
48+
from DataFlow::PathNode source, DataFlow::PathNode sink, TokenBuiltFromUuidConfig config
49+
where config.hasFlowPath(source, sink)
50+
select sink.getNode(), source, sink, "Token built from $@.", source.getNode(), "predictable value"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import uuid
2+
3+
4+
class User:
5+
def __init__(self):
6+
self.token = None
7+
8+
def resetPassword(self):
9+
self.token = uuid.uuid1().hex
10+
11+
12+
user = User()
13+
user.resetPassword()
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
GUIDs (often called UUIDs) are widely used in modern web applications.
8+
One common use for UUIDs is the generation of one-time-use tokens.
9+
These can used for password reset, and e-mail confirmation routines, for example.
10+
</p>
11+
<p>
12+
There are five versions of UUIDs defined in RFC 4122.
13+
Out of the five, four are generated in a predictable manner.
14+
This means it is possible for someone to predict future UUIDs based on a sample
15+
generated by the target application.
16+
</p>
17+
<p>
18+
Version four is the only UUID version expected to be randomly generated.
19+
Therefore, for situations where predictable tokens are not desired (e.g. password reset tokens),
20+
all other versions should be avoided.
21+
</p>
22+
</overview>
23+
24+
<recommendation>
25+
<p>When using GUIDs/UUIDs for generating tokens that should not be predictable, use version four.</p>
26+
</recommendation>
27+
28+
<example>
29+
<p>This example shows a UUID v1 being used for a password reset routine.
30+
</p>
31+
32+
<sample src="TokenBuiltFromUUID.py" />
33+
</example>
34+
35+
<references>
36+
<li>UUID <a href="https://datatracker.ietf.org/doc/html/rfc4122">RFC</a>.</li>
37+
<li>Daniel Thatcher <i>In GUID We Trust</i> <a href="https://www.intruder.io/research/in-guid-we-trust">article</a>.</li>
38+
<li>UUID exploitation <a href="https://github.com/intruder-io/guidtool">tool</a>.</li>
39+
</references>
40+
</qhelp>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @name Predictable token
3+
* @description Tokens used for sensitive tasks, such as, password recovery,
4+
* and email confirmation, should not use predictable values.
5+
* @kind path-problem
6+
* @precision medium
7+
* @problem.severity error
8+
* @security-severity 5
9+
* @id py/predictable-token
10+
* @tags security
11+
* external/cwe/cwe-340
12+
*/
13+
14+
import python
15+
import semmle.python.dataflow.new.DataFlow
16+
import semmle.python.ApiGraphs
17+
import semmle.python.dataflow.new.TaintTracking
18+
import DataFlow::PathGraph
19+
20+
class PredictableResultSource extends DataFlow::Node {
21+
PredictableResultSource() {
22+
exists(API::Node uuidCallRet |
23+
uuidCallRet = API::moduleImport("uuid").getMember(["uuid1", "uuid3", "uuid5"]).getReturn()
24+
|
25+
this = uuidCallRet.asSource()
26+
or
27+
this = uuidCallRet.getMember(["hex", "bytes", "bytes_le"]).asSource()
28+
)
29+
}
30+
}
31+
32+
class TokenAssignmentValueSink extends DataFlow::Node {
33+
TokenAssignmentValueSink() {
34+
exists(string name | name.toLowerCase().matches(["%token", "%code"]) |
35+
exists(DefinitionNode n | n.getValue() = this.asCfgNode() | name = n.(NameNode).getId())
36+
or
37+
exists(DataFlow::AttrWrite aw | aw.getValue() = this | name = aw.getAttributeName())
38+
)
39+
}
40+
}
41+
42+
class TokenBuiltFromUuidConfig extends TaintTracking::Configuration {
43+
TokenBuiltFromUuidConfig() { this = "TokenBuiltFromUuidConfig" }
44+
45+
override predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
46+
47+
override predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
48+
49+
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
50+
exists(DataFlow::CallCfgNode call |
51+
call = API::builtin("str").getACall() and
52+
nodeFrom = call.getArg(0) and
53+
nodeTo = call
54+
)
55+
}
56+
}
57+
58+
from DataFlow::PathNode source, DataFlow::PathNode sink, TokenBuiltFromUuidConfig config
59+
where config.hasFlowPath(source, sink)
60+
select sink.getNode(), source, sink, "Token built from $@.", source.getNode(), "predictable value"

0 commit comments

Comments
 (0)