Skip to content

Commit 066ffb7

Browse files
author
Daniel Santos
committed
Tokens built from predictable UUIDs
1 parent e566357 commit 066ffb7

File tree

6 files changed

+248
-0
lines changed

6 files changed

+248
-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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 =
22+
API::moduleImport("uuid").getMember(["v1", "v2", "v3", "v5"]).getACall().getReturn()
23+
|
24+
this = uuidCallRet.asSource()
25+
)
26+
}
27+
}
28+
29+
class TokenAssignmentValueSink extends DataFlow::Node {
30+
TokenAssignmentValueSink() {
31+
exists(PropWrite pw | this = pw.getRhs() |
32+
pw.getPropertyName().toLowerCase().matches(["%token", "%code"])
33+
)
34+
or
35+
exists(AssignExpr ae | this = ae.getRhs().flow() |
36+
ae.getLhs()
37+
.(VariableAccess)
38+
.getVariable()
39+
.getName()
40+
.toLowerCase()
41+
.matches(["%token", "%code"])
42+
)
43+
}
44+
}
45+
46+
class TokenBuiltFromUUIDConfig extends TaintTracking::Configuration {
47+
TokenBuiltFromUUIDConfig() { this = "TokenBuiltFromUUIDConfig" }
48+
49+
override predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
50+
51+
override predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
52+
}
53+
54+
from DataFlow::PathNode source, DataFlow::PathNode sink, TokenBuiltFromUUIDConfig config
55+
where config.hasFlowPath(source, sink)
56+
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: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 =
24+
API::moduleImport("uuid")
25+
.getMember(["uuid1", "uuid2", "uuid3", "uuid5"])
26+
.getACall()
27+
.getReturn()
28+
|
29+
this = uuidCallRet.asSource()
30+
or
31+
this = uuidCallRet.getMember(["hex", "bytes", "bytes_le"]).asSource()
32+
)
33+
}
34+
}
35+
36+
class TokenAssignmentValueSink extends DataFlow::Node {
37+
TokenAssignmentValueSink() {
38+
exists(Assign a, Expr target | this = DataFlow::exprNode(a.getValue()) |
39+
target = a.getATarget() and
40+
(target instanceof Attribute or target instanceof Name) and
41+
(
42+
target.(Attribute).getName().toLowerCase().matches(["%token", "%code"])
43+
or
44+
target.(Name).getId().toLowerCase().matches(["%token", "%code"])
45+
)
46+
)
47+
}
48+
}
49+
50+
class TokenBuiltFromUUIDConfig extends TaintTracking::Configuration {
51+
TokenBuiltFromUUIDConfig() { this = "TokenBuiltFromUUIDConfig" }
52+
53+
override predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
54+
55+
override predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
56+
57+
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
58+
exists(Call call, Name name |
59+
call.getFunc() = name and
60+
name.getId() = "str" and
61+
nodeFrom = DataFlow::exprNode(call.getArg(0)) and
62+
nodeTo = DataFlow::exprNode(call)
63+
)
64+
}
65+
}
66+
67+
from DataFlow::PathNode source, DataFlow::PathNode sink, TokenBuiltFromUUIDConfig config
68+
where config.hasFlowPath(source, sink)
69+
select sink.getNode(), source, sink, "Token built from $@.", source.getNode(), "predictable value"

0 commit comments

Comments
 (0)