mirror of
https://github.com/github/codeql.git
synced 2026-04-29 10:45:15 +02:00
Merge pull request #5284 from yoff/python-port-insecure-protocol
Python: port py/insecure-protocol
This commit is contained in:
103
python/ql/src/Security/CWE-327/FluentApiModel.qll
Normal file
103
python/ql/src/Security/CWE-327/FluentApiModel.qll
Normal file
@@ -0,0 +1,103 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import TlsLibraryModel
|
||||
|
||||
/**
|
||||
* Configuration to determine the state of a context being used to create
|
||||
* a connection. There is one configuration for each pair of `TlsLibrary` and `ProtocolVersion`,
|
||||
* such that a single configuration only tracks contexts where a specific `ProtocolVersion` is allowed.
|
||||
*
|
||||
* The state is in terms of whether a specific protocol is allowed. This is
|
||||
* either true or false when the context is created and can then be modified
|
||||
* later by either restricting or unrestricting the protocol (see the predicates
|
||||
* `isRestriction` and `isUnrestriction`).
|
||||
*
|
||||
* Since we are interested in the final state, we want the flow to start from
|
||||
* the last unrestriction, so we disallow flow into unrestrictions. We also
|
||||
* model the creation as an unrestriction of everything it allows, to account
|
||||
* for the common case where the creation plays the role of "last unrestriction".
|
||||
*
|
||||
* Since we really want "the last unrestriction, not nullified by a restriction",
|
||||
* we also disallow flow into restrictions.
|
||||
*/
|
||||
class InsecureContextConfiguration extends DataFlow::Configuration {
|
||||
TlsLibrary library;
|
||||
ProtocolVersion tracked_version;
|
||||
|
||||
InsecureContextConfiguration() {
|
||||
this = library + "Allows" + tracked_version and
|
||||
tracked_version.isInsecure()
|
||||
}
|
||||
|
||||
ProtocolVersion getTrackedVersion() { result = tracked_version }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { this.isUnrestriction(source) }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = library.connection_creation().getContext()
|
||||
}
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) {
|
||||
this.isRestriction(node)
|
||||
or
|
||||
this.isUnrestriction(node)
|
||||
}
|
||||
|
||||
private predicate isRestriction(DataFlow::Node node) {
|
||||
exists(ProtocolRestriction r |
|
||||
r = library.protocol_restriction() and
|
||||
r.getRestriction() = tracked_version
|
||||
|
|
||||
node = r.getContext()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isUnrestriction(DataFlow::Node node) {
|
||||
exists(ProtocolUnrestriction pu |
|
||||
pu = library.protocol_unrestriction() and
|
||||
pu.getUnrestriction() = tracked_version
|
||||
|
|
||||
node = pu.getContext()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `conectionCreation` marks the creation of a connetion based on the contex
|
||||
* found at `contextOrigin` and allowing `insecure_version`.
|
||||
*
|
||||
* `specific` is true iff the context is configured for a specific protocol version (`ssl.PROTOCOL_TLSv1_2`) rather
|
||||
* than for a family of protocols (`ssl.PROTOCOL_TLS`).
|
||||
*/
|
||||
predicate unsafe_connection_creation_with_context(
|
||||
DataFlow::Node connectionCreation, ProtocolVersion insecure_version, DataFlow::Node contextOrigin,
|
||||
boolean specific
|
||||
) {
|
||||
// Connection created from a context allowing `insecure_version`.
|
||||
exists(InsecureContextConfiguration c | c.hasFlow(contextOrigin, connectionCreation) |
|
||||
insecure_version = c.getTrackedVersion() and
|
||||
specific = false
|
||||
)
|
||||
or
|
||||
// Connection created from a context specifying `insecure_version`.
|
||||
exists(TlsLibrary l |
|
||||
connectionCreation = l.insecure_connection_creation(insecure_version) and
|
||||
contextOrigin = connectionCreation and
|
||||
specific = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `conectionCreation` marks the creation of a connetion witout reference to a context
|
||||
* and allowing `insecure_version`.
|
||||
*/
|
||||
predicate unsafe_connection_creation_without_context(
|
||||
DataFlow::CallCfgNode connectionCreation, string insecure_version
|
||||
) {
|
||||
exists(TlsLibrary l | connectionCreation = l.insecure_connection_creation(insecure_version))
|
||||
}
|
||||
|
||||
/** Holds if `contextCreation` is creating a context tied to a specific insecure version. */
|
||||
predicate unsafe_context_creation(DataFlow::CallCfgNode contextCreation, string insecure_version) {
|
||||
exists(TlsLibrary l | contextCreation = l.insecure_context_creation(insecure_version))
|
||||
}
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
<p>
|
||||
Ensure that a modern, strong protocol is used. All versions of SSL,
|
||||
and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
|
||||
above is strongly recommended.
|
||||
and TLS versions 1.0 and 1.1 are known to be vulnerable to attacks.
|
||||
Using TLS 1.2 or above is strongly recommended.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
@@ -30,20 +30,35 @@
|
||||
|
||||
<p>
|
||||
All cases should be updated to use a secure protocol, such as
|
||||
<code>PROTOCOL_TLSv1_1</code>.
|
||||
<code>PROTOCOL_TLSv1_2</code>.
|
||||
</p>
|
||||
<p>
|
||||
Note that <code>ssl.wrap_socket</code> has been deprecated in
|
||||
Python 3.7. A preferred alternative is to use
|
||||
<code>ssl.SSLContext</code>, which is supported in Python 2.7.9 and
|
||||
3.2 and later versions.
|
||||
Python 3.7. The recommended alternatives are:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>ssl.SSLContext</code> - supported in Python 2.7.9,
|
||||
3.2, and later versions</li>
|
||||
<li><code>ssl.create_default_context</code> - a convenience function,
|
||||
supported in Python 3.4 and later versions.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Even when you use these alternatives, you should
|
||||
ensure that a safe protocol is used. The following code illustrates
|
||||
how to use flags (available since Python 3.2) or the `minimum_version`
|
||||
field (favored since Python 3.7) to restrict the protocols accepted when
|
||||
creating a connection.
|
||||
</p>
|
||||
|
||||
<sample src="examples/secure_default_protocol.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"> Transport Layer Security</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext"> class ssl.SSLContext</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.wrap_socket"> ssl.wrap_socket</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#functions-constants-and-exceptions"> notes on context creation</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl-security"> notes on security considerations</a>.</li>
|
||||
<li>pyOpenSSL documentation: <a href="https://pyopenssl.org/en/stable/api/ssl.html"> An interface to the SSL-specific parts of OpenSSL</a>.</li>
|
||||
</references>
|
||||
|
||||
|
||||
@@ -10,86 +10,77 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import FluentApiModel
|
||||
|
||||
private ModuleValue the_ssl_module() { result = Module::named("ssl") }
|
||||
|
||||
FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
|
||||
|
||||
ClassValue ssl_Context_class() { result = the_ssl_module().attr("SSLContext") }
|
||||
|
||||
private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SSL") }
|
||||
|
||||
ClassValue the_pyOpenSSL_Context_class() { result = Value::named("pyOpenSSL.SSL.Context") }
|
||||
|
||||
string insecure_version_name() {
|
||||
// For `pyOpenSSL.SSL`
|
||||
result = "SSLv2_METHOD" or
|
||||
result = "SSLv23_METHOD" or
|
||||
result = "SSLv3_METHOD" or
|
||||
result = "TLSv1_METHOD" or
|
||||
// For the `ssl` module
|
||||
result = "PROTOCOL_SSLv2" or
|
||||
result = "PROTOCOL_SSLv3" or
|
||||
result = "PROTOCOL_SSLv23" or
|
||||
result = "PROTOCOL_TLS" or
|
||||
result = "PROTOCOL_TLSv1"
|
||||
}
|
||||
|
||||
/*
|
||||
* A syntactic check for cases where points-to analysis cannot infer the presence of
|
||||
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
|
||||
* library.
|
||||
*/
|
||||
|
||||
bindingset[named_argument]
|
||||
predicate probable_insecure_ssl_constant(
|
||||
CallNode call, string insecure_version, string named_argument
|
||||
) {
|
||||
exists(ControlFlowNode arg |
|
||||
arg = call.getArgByName(named_argument) or
|
||||
arg = call.getArg(0)
|
||||
|
|
||||
arg.(AttrNode).getObject(insecure_version).pointsTo(the_ssl_module())
|
||||
// Helper for pretty printer `configName`.
|
||||
// This is a consequence of missing pretty priting.
|
||||
// We do not want to evaluate our bespoke pretty printer
|
||||
// for all `DataFlow::Node`s so we define a sub class of interesting ones.
|
||||
class ProtocolConfiguration extends DataFlow::Node {
|
||||
ProtocolConfiguration() {
|
||||
unsafe_connection_creation_with_context(_, _, this, _)
|
||||
or
|
||||
arg.(NameNode).getId() = insecure_version and
|
||||
exists(Import imp |
|
||||
imp.getAnImportedModuleName() = "ssl" and
|
||||
imp.getAName().getAsname().(Name).getId() = insecure_version
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate unsafe_ssl_wrap_socket_call(
|
||||
CallNode call, string method_name, string insecure_version, string named_argument
|
||||
) {
|
||||
(
|
||||
call = ssl_wrap_socket().getACall() and
|
||||
method_name = "deprecated method ssl.wrap_socket" and
|
||||
named_argument = "ssl_version"
|
||||
unsafe_connection_creation_without_context(this, _)
|
||||
or
|
||||
call = ssl_Context_class().getACall() and
|
||||
named_argument = "protocol" and
|
||||
method_name = "ssl.SSLContext"
|
||||
) and
|
||||
insecure_version = insecure_version_name() and
|
||||
(
|
||||
call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
|
||||
unsafe_context_creation(this, _)
|
||||
}
|
||||
|
||||
AstNode getNode() { result = this.asCfgNode().(CallNode).getFunction().getNode() }
|
||||
}
|
||||
|
||||
// Helper for pretty printer `callName`.
|
||||
// This is a consequence of missing pretty priting.
|
||||
// We do not want to evaluate our bespoke pretty printer
|
||||
// for all `AstNode`s so we define a sub class of interesting ones.
|
||||
//
|
||||
// Note that AstNode is abstract and AstNode_ is a library class, so
|
||||
// we have to extend @py_ast_node.
|
||||
class Nameable extends @py_ast_node {
|
||||
Nameable() {
|
||||
this = any(ProtocolConfiguration pc).getNode()
|
||||
or
|
||||
probable_insecure_ssl_constant(call, insecure_version, named_argument)
|
||||
)
|
||||
exists(Nameable attr | this = attr.(Attribute).getObject())
|
||||
}
|
||||
|
||||
string toString() { result = "AstNode" }
|
||||
}
|
||||
|
||||
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
|
||||
call = the_pyOpenSSL_Context_class().getACall() and
|
||||
insecure_version = insecure_version_name() and
|
||||
call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
|
||||
}
|
||||
|
||||
from CallNode call, string method_name, string insecure_version
|
||||
where
|
||||
unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
|
||||
string callName(Nameable call) {
|
||||
result = call.(Name).getId()
|
||||
or
|
||||
unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
|
||||
select call,
|
||||
"Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
|
||||
"."
|
||||
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
|
||||
}
|
||||
|
||||
string configName(ProtocolConfiguration protocolConfiguration) {
|
||||
result =
|
||||
"call to " + callName(protocolConfiguration.asCfgNode().(CallNode).getFunction().getNode())
|
||||
or
|
||||
not protocolConfiguration.asCfgNode() instanceof CallNode and
|
||||
not protocolConfiguration instanceof ContextCreation and
|
||||
result = "context modification"
|
||||
}
|
||||
|
||||
string verb(boolean specific) {
|
||||
specific = true and result = "specified"
|
||||
or
|
||||
specific = false and result = "allowed"
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::Node connectionCreation, string insecure_version, DataFlow::Node protocolConfiguration,
|
||||
boolean specific
|
||||
where
|
||||
unsafe_connection_creation_with_context(connectionCreation, insecure_version,
|
||||
protocolConfiguration, specific)
|
||||
or
|
||||
unsafe_connection_creation_without_context(connectionCreation, insecure_version) and
|
||||
protocolConfiguration = connectionCreation and
|
||||
specific = true
|
||||
or
|
||||
unsafe_context_creation(protocolConfiguration, insecure_version) and
|
||||
connectionCreation = protocolConfiguration and
|
||||
specific = true
|
||||
select connectionCreation,
|
||||
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
|
||||
protocolConfiguration, configName(protocolConfiguration)
|
||||
|
||||
83
python/ql/src/Security/CWE-327/PyOpenSSL.qll
Normal file
83
python/ql/src/Security/CWE-327/PyOpenSSL.qll
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Provides modeling of SSL/TLS functionality of the `OpenSSL` module from the `pyOpenSSL` PyPI package.
|
||||
* See https://www.pyopenssl.org/en/stable/
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
import TlsLibraryModel
|
||||
|
||||
class PyOpenSSLContextCreation extends ContextCreation, DataFlow::CallCfgNode {
|
||||
PyOpenSSLContextCreation() {
|
||||
this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Context").getACall()
|
||||
}
|
||||
|
||||
override string getProtocol() {
|
||||
exists(ControlFlowNode protocolArg, PyOpenSSL pyo |
|
||||
protocolArg in [node.getArg(0), node.getArgByName("method")]
|
||||
|
|
||||
protocolArg =
|
||||
[pyo.specific_version(result).getAUse(), pyo.unspecific_version(result).getAUse()]
|
||||
.asCfgNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionCall extends ConnectionCreation, DataFlow::CallCfgNode {
|
||||
ConnectionCall() {
|
||||
this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Connection").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::CfgNode getContext() {
|
||||
result.getNode() in [node.getArg(0), node.getArgByName("context")]
|
||||
}
|
||||
}
|
||||
|
||||
// This cannot be used to unrestrict,
|
||||
// see https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_options
|
||||
class SetOptionsCall extends ProtocolRestriction, DataFlow::CallCfgNode {
|
||||
SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
|
||||
|
||||
override DataFlow::CfgNode getContext() {
|
||||
result.getNode() = node.getFunction().(AttrNode).getObject()
|
||||
}
|
||||
|
||||
override ProtocolVersion getRestriction() {
|
||||
API::moduleImport("OpenSSL").getMember("SSL").getMember("OP_NO_" + result).getAUse().asCfgNode() in [
|
||||
node.getArg(0), node.getArgByName("options")
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class UnspecificPyOpenSSLContextCreation extends PyOpenSSLContextCreation, UnspecificContextCreation {
|
||||
UnspecificPyOpenSSLContextCreation() { library instanceof PyOpenSSL }
|
||||
}
|
||||
|
||||
class PyOpenSSL extends TlsLibrary {
|
||||
PyOpenSSL() { this = "pyOpenSSL" }
|
||||
|
||||
override string specific_version_name(ProtocolVersion version) { result = version + "_METHOD" }
|
||||
|
||||
override string unspecific_version_name(ProtocolFamily family) {
|
||||
// `"TLS_METHOD"` is not actually available in pyOpenSSL yet, but should be coming soon..
|
||||
result = family + "_METHOD"
|
||||
}
|
||||
|
||||
override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
|
||||
|
||||
override ContextCreation default_context_creation() { none() }
|
||||
|
||||
override ContextCreation specific_context_creation() {
|
||||
result instanceof PyOpenSSLContextCreation
|
||||
}
|
||||
|
||||
override DataFlow::Node insecure_connection_creation(ProtocolVersion version) { none() }
|
||||
|
||||
override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
|
||||
|
||||
override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
|
||||
|
||||
override ProtocolUnrestriction protocol_unrestriction() {
|
||||
result instanceof UnspecificPyOpenSSLContextCreation
|
||||
}
|
||||
}
|
||||
24
python/ql/src/Security/CWE-327/README.md
Normal file
24
python/ql/src/Security/CWE-327/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Current status (Feb 2021)
|
||||
|
||||
This should be kept up to date; the world is moving fast and protocols are being broken.
|
||||
|
||||
## Protocols
|
||||
|
||||
- All versions of SSL are insecure
|
||||
- TLS 1.0 and TLS 1.1 are insecure
|
||||
- TLS 1.2 have some issues. but TLS 1.3 is not widely supported
|
||||
|
||||
## Conection methods
|
||||
|
||||
- `ssl.wrap_socket` is creating insecure connections, use `SSLContext.wrap_socket` instead. [link](https://docs.python.org/3/library/ssl.html#ssl.wrap_socket)
|
||||
> Deprecated since version 3.7: Since Python 3.2 and 2.7.9, it is recommended to use the `SSLContext.wrap_socket()` instead of `wrap_socket()`. The top-level function is limited and creates an insecure client socket without server name indication or hostname matching.
|
||||
- Default constructors are fine, a fluent API is used to constrain possible protocols later.
|
||||
|
||||
## Current recomendation
|
||||
|
||||
TLS 1.2 or TLS 1.3
|
||||
|
||||
## Queries
|
||||
|
||||
- `InsecureProtocol` detects uses of insecure protocols.
|
||||
- `InsecureDefaultProtocol` detect default constructions, this is no longer unsafe.
|
||||
214
python/ql/src/Security/CWE-327/Ssl.qll
Normal file
214
python/ql/src/Security/CWE-327/Ssl.qll
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Provides modeling of SSL/TLS functionality of the `ssl` module from the standard library.
|
||||
* See https://docs.python.org/3.9/library/ssl.html
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
import TlsLibraryModel
|
||||
|
||||
class SSLContextCreation extends ContextCreation, DataFlow::CallCfgNode {
|
||||
SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
|
||||
|
||||
override string getProtocol() {
|
||||
exists(ControlFlowNode protocolArg, Ssl ssl |
|
||||
protocolArg in [node.getArg(0), node.getArgByName("protocol")]
|
||||
|
|
||||
protocolArg =
|
||||
[ssl.specific_version(result).getAUse(), ssl.unspecific_version(result).getAUse()]
|
||||
.asCfgNode()
|
||||
)
|
||||
or
|
||||
not exists(node.getAnArg()) and
|
||||
result = "TLS"
|
||||
}
|
||||
}
|
||||
|
||||
class SSLDefaultContextCreation extends ContextCreation {
|
||||
SSLDefaultContextCreation() {
|
||||
this = API::moduleImport("ssl").getMember("create_default_context").getACall()
|
||||
}
|
||||
|
||||
// Allowed insecure versions are "TLSv1" and "TLSv1_1"
|
||||
// see https://docs.python.org/3/library/ssl.html#context-creation
|
||||
override string getProtocol() { result = "TLS" }
|
||||
}
|
||||
|
||||
/** Gets a reference to an `ssl.Context` instance. */
|
||||
API::Node sslContextInstance() {
|
||||
result = API::moduleImport("ssl").getMember(["SSLContext", "create_default_context"]).getReturn()
|
||||
}
|
||||
|
||||
class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode {
|
||||
WrapSocketCall() { this = sslContextInstance().getMember("wrap_socket").getACall() }
|
||||
|
||||
override DataFlow::Node getContext() {
|
||||
result = this.getFunction().(DataFlow::AttrRead).getObject()
|
||||
}
|
||||
}
|
||||
|
||||
class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode {
|
||||
ProtocolVersion restriction;
|
||||
|
||||
OptionsAugOr() {
|
||||
exists(AugAssign aa, AttrNode attr, Expr flag |
|
||||
aa.getOperation().getOp() instanceof BitOr and
|
||||
aa.getTarget() = attr.getNode() and
|
||||
attr.getName() = "options" and
|
||||
attr.getObject() = node and
|
||||
flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
|
||||
(
|
||||
aa.getValue() = flag
|
||||
or
|
||||
impliesBitSet(aa.getValue(), flag, false, false)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getRestriction() { result = restriction }
|
||||
}
|
||||
|
||||
class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode {
|
||||
ProtocolVersion restriction;
|
||||
|
||||
OptionsAugAndNot() {
|
||||
exists(AugAssign aa, AttrNode attr, Expr flag, UnaryExpr notFlag |
|
||||
aa.getOperation().getOp() instanceof BitAnd and
|
||||
aa.getTarget() = attr.getNode() and
|
||||
attr.getName() = "options" and
|
||||
attr.getObject() = node and
|
||||
notFlag.getOp() instanceof Invert and
|
||||
notFlag.getOperand() = flag and
|
||||
flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
|
||||
(
|
||||
aa.getValue() = notFlag
|
||||
or
|
||||
impliesBitSet(aa.getValue(), notFlag, true, true)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getUnrestriction() { result = restriction }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if
|
||||
* for every bit, _b_:
|
||||
* `wholeHasBitSet` represents that _b_ is set in `whole`
|
||||
* implies
|
||||
* `partHasBitSet` represents that _b_ is set in `part`
|
||||
*
|
||||
* As an example take `whole` = `part1 & part2`. Then
|
||||
* `impliesBitSet(whole, part1, true, true)` holds
|
||||
* because for any bit in `whole`, if that bit is set it must also be set in `part1`.
|
||||
*
|
||||
* Similarly for `whole` = `part1 | part2`. Here
|
||||
* `impliesBitSet(whole, part1, false, false)` holds
|
||||
* because for any bit in `whole`, if that bit is not set, it cannot be set in `part1`.
|
||||
*/
|
||||
predicate impliesBitSet(BinaryExpr whole, Expr part, boolean partHasBitSet, boolean wholeHasBitSet) {
|
||||
whole.getOp() instanceof BitAnd and
|
||||
(
|
||||
wholeHasBitSet = true and partHasBitSet = true and part in [whole.getLeft(), whole.getRight()]
|
||||
or
|
||||
wholeHasBitSet = true and
|
||||
impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
|
||||
)
|
||||
or
|
||||
whole.getOp() instanceof BitOr and
|
||||
(
|
||||
wholeHasBitSet = false and partHasBitSet = false and part in [whole.getLeft(), whole.getRight()]
|
||||
or
|
||||
wholeHasBitSet = false and
|
||||
impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
|
||||
)
|
||||
}
|
||||
|
||||
class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, DataFlow::CfgNode {
|
||||
ProtocolVersion restriction;
|
||||
|
||||
ContextSetVersion() {
|
||||
exists(DataFlow::AttrWrite aw |
|
||||
aw.getObject().asCfgNode() = node and
|
||||
aw.getAttributeName() = "minimum_version" and
|
||||
aw.getValue() =
|
||||
API::moduleImport("ssl").getMember("TLSVersion").getMember(restriction).getAUse()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getRestriction() { result.lessThan(restriction) }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
restriction = result or restriction.lessThan(result)
|
||||
}
|
||||
}
|
||||
|
||||
class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContextCreation {
|
||||
UnspecificSSLContextCreation() { library instanceof Ssl }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
result = UnspecificContextCreation.super.getUnrestriction() and
|
||||
// These are turned off by default since Python 3.6
|
||||
// see https://docs.python.org/3.6/library/ssl.html#ssl.SSLContext
|
||||
not result in ["SSLv2", "SSLv3"]
|
||||
}
|
||||
}
|
||||
|
||||
class UnspecificSSLDefaultContextCreation extends SSLDefaultContextCreation, ProtocolUnrestriction {
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
// see https://docs.python.org/3/library/ssl.html#ssl.create_default_context
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
}
|
||||
}
|
||||
|
||||
class Ssl extends TlsLibrary {
|
||||
Ssl() { this = "ssl" }
|
||||
|
||||
override string specific_version_name(ProtocolVersion version) { result = "PROTOCOL_" + version }
|
||||
|
||||
override string unspecific_version_name(ProtocolFamily family) {
|
||||
family = "SSLv23" and result = "PROTOCOL_" + family
|
||||
or
|
||||
family = "TLS" and result = "PROTOCOL_" + family + ["", "_CLIENT", "_SERVER"]
|
||||
}
|
||||
|
||||
override API::Node version_constants() { result = API::moduleImport("ssl") }
|
||||
|
||||
override ContextCreation default_context_creation() {
|
||||
result instanceof SSLDefaultContextCreation
|
||||
}
|
||||
|
||||
override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
|
||||
|
||||
override DataFlow::CallCfgNode insecure_connection_creation(ProtocolVersion version) {
|
||||
result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and
|
||||
this.specific_version(version).getAUse() = result.getArgByName("ssl_version") and
|
||||
version.isInsecure()
|
||||
}
|
||||
|
||||
override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
|
||||
|
||||
override ProtocolRestriction protocol_restriction() {
|
||||
result instanceof OptionsAugOr
|
||||
or
|
||||
result instanceof ContextSetVersion
|
||||
}
|
||||
|
||||
override ProtocolUnrestriction protocol_unrestriction() {
|
||||
result instanceof OptionsAugAndNot
|
||||
or
|
||||
result instanceof ContextSetVersion
|
||||
or
|
||||
result instanceof UnspecificSSLContextCreation
|
||||
or
|
||||
result instanceof UnspecificSSLDefaultContextCreation
|
||||
}
|
||||
}
|
||||
137
python/ql/src/Security/CWE-327/TlsLibraryModel.qll
Normal file
137
python/ql/src/Security/CWE-327/TlsLibraryModel.qll
Normal file
@@ -0,0 +1,137 @@
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
import Ssl
|
||||
import PyOpenSSL
|
||||
|
||||
/**
|
||||
* A specific protocol version of SSL or TLS.
|
||||
*/
|
||||
class ProtocolVersion extends string {
|
||||
ProtocolVersion() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] }
|
||||
|
||||
/** Gets a `ProtocolVersion` that is less than this `ProtocolVersion`, if any. */
|
||||
predicate lessThan(ProtocolVersion version) {
|
||||
this = "SSLv2" and version = "SSLv3"
|
||||
or
|
||||
this = "TLSv1" and version = ["TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
or
|
||||
this = ["TLSv1", "TLSv1_1"] and version = ["TLSv1_2", "TLSv1_3"]
|
||||
or
|
||||
this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3"
|
||||
}
|
||||
|
||||
/** Holds if this protocol version is known to be insecure. */
|
||||
predicate isInsecure() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] }
|
||||
}
|
||||
|
||||
/** An unspecific protocol version */
|
||||
class ProtocolFamily extends string {
|
||||
ProtocolFamily() { this in ["SSLv23", "TLS"] }
|
||||
}
|
||||
|
||||
/** The creation of a context. */
|
||||
abstract class ContextCreation extends DataFlow::Node {
|
||||
/** Gets the protocol version or family for this context. */
|
||||
abstract string getProtocol();
|
||||
}
|
||||
|
||||
/** The creation of a connection from a context. */
|
||||
abstract class ConnectionCreation extends DataFlow::Node {
|
||||
/** Gets the context used to create the connection. */
|
||||
abstract DataFlow::Node getContext();
|
||||
}
|
||||
|
||||
/** A context is being restricted on which protocols it can accepts. */
|
||||
abstract class ProtocolRestriction extends DataFlow::Node {
|
||||
/** Gets the context being restricted. */
|
||||
abstract DataFlow::Node getContext();
|
||||
|
||||
/** Gets the protocol version being disallowed. */
|
||||
abstract ProtocolVersion getRestriction();
|
||||
}
|
||||
|
||||
/** A context is being relaxed on which protocols it can accepts. */
|
||||
abstract class ProtocolUnrestriction extends DataFlow::Node {
|
||||
/** Gets the context being relaxed. */
|
||||
abstract DataFlow::Node getContext();
|
||||
|
||||
/** Gets the protocol version being allowed. */
|
||||
abstract ProtocolVersion getUnrestriction();
|
||||
}
|
||||
|
||||
/**
|
||||
* A context is being created with a range of allowed protocols.
|
||||
* This also serves as unrestricting these protocols.
|
||||
*/
|
||||
abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrestriction {
|
||||
TlsLibrary library;
|
||||
ProtocolFamily family;
|
||||
|
||||
UnspecificContextCreation() { this.getProtocol() = family }
|
||||
|
||||
override DataFlow::CfgNode getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
// There is only one family, the two names are aliases in OpenSSL.
|
||||
// see https://github.com/openssl/openssl/blob/13888e797c5a3193e91d71e5f5a196a2d68d266f/include/openssl/ssl.h.in#L1953-L1955
|
||||
family in ["SSLv23", "TLS"] and
|
||||
// see https://docs.python.org/3/library/ssl.html#ssl-contexts
|
||||
result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
}
|
||||
}
|
||||
|
||||
/** A model of a SSL/TLS library. */
|
||||
abstract class TlsLibrary extends string {
|
||||
bindingset[this]
|
||||
TlsLibrary() { any() }
|
||||
|
||||
/** The name of a specific protocol version. */
|
||||
abstract string specific_version_name(ProtocolVersion version);
|
||||
|
||||
/** Gets a name, which is a member of `version_constants`, that can be used to specify the protocol family `family`. */
|
||||
abstract string unspecific_version_name(ProtocolFamily family);
|
||||
|
||||
/** Gets an API node representing the module or class holding the version constants. */
|
||||
abstract API::Node version_constants();
|
||||
|
||||
/** Gets an API node representing a specific protocol version. */
|
||||
API::Node specific_version(ProtocolVersion version) {
|
||||
result = version_constants().getMember(specific_version_name(version))
|
||||
}
|
||||
|
||||
/** Gets an API node representing the protocol family `family`. */
|
||||
API::Node unspecific_version(ProtocolFamily family) {
|
||||
result = version_constants().getMember(unspecific_version_name(family))
|
||||
}
|
||||
|
||||
/** Gets a creation of a context with a default protocol. */
|
||||
abstract ContextCreation default_context_creation();
|
||||
|
||||
/** Gets a creation of a context with a specific protocol. */
|
||||
abstract ContextCreation specific_context_creation();
|
||||
|
||||
/** Gets a creation of a context with a specific protocol version, known to be insecure. */
|
||||
ContextCreation insecure_context_creation(ProtocolVersion version) {
|
||||
result in [specific_context_creation(), default_context_creation()] and
|
||||
result.getProtocol() = version and
|
||||
version.isInsecure()
|
||||
}
|
||||
|
||||
/** Gets a context that was created using `family`, known to have insecure instances. */
|
||||
ContextCreation unspecific_context_creation(ProtocolFamily family) {
|
||||
result in [specific_context_creation(), default_context_creation()] and
|
||||
result.getProtocol() = family
|
||||
}
|
||||
|
||||
/** Gets a dataflow node representing a connection being created in an insecure manner, not from a context. */
|
||||
abstract DataFlow::Node insecure_connection_creation(ProtocolVersion version);
|
||||
|
||||
/** Gets a dataflow node representing a connection being created from a context. */
|
||||
abstract ConnectionCreation connection_creation();
|
||||
|
||||
/** Gets a dataflow node representing a context being restricted on which protocols it can accepts. */
|
||||
abstract ProtocolRestriction protocol_restriction();
|
||||
|
||||
/** Gets a dataflow node representing a context being relaxed on which protocols it can accepts. */
|
||||
abstract ProtocolUnrestriction protocol_unrestriction();
|
||||
}
|
||||
Reference in New Issue
Block a user