mirror of
https://github.com/github/codeql.git
synced 2026-05-02 12:15:17 +02:00
Tokens built from predictable UUIDs
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import uuid
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(self):
|
||||
self.token = None
|
||||
|
||||
def resetPassword(self):
|
||||
self.token = uuid.uuid1().hex
|
||||
|
||||
|
||||
user = User()
|
||||
user.resetPassword()
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
GUIDs (often called UUIDs) are widely used in modern web applications.
|
||||
One common use for UUIDs is the generation of one-time-use tokens.
|
||||
These can used for password reset, and e-mail confirmation routines, for example.
|
||||
</p>
|
||||
<p>
|
||||
There are five versions of UUIDs defined in RFC 4122.
|
||||
Out of the five, four are generated in a predictable manner.
|
||||
This means it is possible for someone to predict future UUIDs based on a sample
|
||||
generated by the target application.
|
||||
</p>
|
||||
<p>
|
||||
Version four is the only UUID version expected to be randomly generated.
|
||||
Therefore, for situations where predictable tokens are not desired (e.g. password reset tokens),
|
||||
all other versions should be avoided.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>When using GUIDs/UUIDs for generating tokens that should not be predictable, use version four.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>This example shows a UUID v1 being used for a password reset routine.
|
||||
</p>
|
||||
|
||||
<sample src="TokenBuiltFromUUID.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>UUID <a href="https://datatracker.ietf.org/doc/html/rfc4122">RFC</a>.</li>
|
||||
<li>Daniel Thatcher <i>In GUID We Trust<i> <a href="https://www.intruder.io/research/in-guid-we-trust">article</a>.</li>
|
||||
<li>UUID exploitation <a href="https://github.com/intruder-io/guidtool">tool</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @name Predictable token
|
||||
* @description Tokens used for sensitive tasks, such as, password recovery,
|
||||
* and email confirmation, should not use predictable values.
|
||||
* @kind path-problem
|
||||
* @precision medium
|
||||
* @problem.severity error
|
||||
* @security-severity 5
|
||||
* @id py/predictable-token
|
||||
* @tags security
|
||||
* external/cwe/cwe-340
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class PredictableResultSource extends DataFlow::Node {
|
||||
PredictableResultSource() {
|
||||
exists(API::Node uuidCallRet |
|
||||
uuidCallRet =
|
||||
API::moduleImport("uuid")
|
||||
.getMember(["uuid1", "uuid2", "uuid3", "uuid5"])
|
||||
.getACall()
|
||||
.getReturn()
|
||||
|
|
||||
this = uuidCallRet.asSource()
|
||||
or
|
||||
this = uuidCallRet.getMember(["hex", "bytes", "bytes_le"]).asSource()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TokenAssignmentValueSink extends DataFlow::Node {
|
||||
TokenAssignmentValueSink() {
|
||||
exists(Assign a, Expr target | this = DataFlow::exprNode(a.getValue()) |
|
||||
target = a.getATarget() and
|
||||
(target instanceof Attribute or target instanceof Name) and
|
||||
(
|
||||
target.(Attribute).getName().toLowerCase().matches(["%token", "%code"])
|
||||
or
|
||||
target.(Name).getId().toLowerCase().matches(["%token", "%code"])
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TokenBuiltFromUUIDConfig extends TaintTracking::Configuration {
|
||||
TokenBuiltFromUUIDConfig() { this = "TokenBuiltFromUUIDConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(Call call, Name name |
|
||||
call.getFunc() = name and
|
||||
name.getId() = "str" and
|
||||
nodeFrom = DataFlow::exprNode(call.getArg(0)) and
|
||||
nodeTo = DataFlow::exprNode(call)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, TokenBuiltFromUUIDConfig config
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Token built from $@.", source.getNode(), "predictable value"
|
||||
Reference in New Issue
Block a user