diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll new file mode 100644 index 00000000000..4c2278b8694 --- /dev/null +++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll @@ -0,0 +1,54 @@ +import python +import TlsLibraryModel + +class InsecureContextConfiguration extends DataFlow::Configuration { + TlsLibrary library; + + InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] } + + override predicate isSource(DataFlow::Node source) { + source = library.unspecific_context_creation() + } + + override predicate isSink(DataFlow::Node sink) { + sink = library.connection_creation().getContext() + } + + abstract string flag(); + + override predicate isBarrierOut(DataFlow::Node node) { + exists(ProtocolRestriction r | + r = library.protocol_restriction() and + node = r.getContext() and + r.getRestriction() = flag() + ) + } +} + +class AllowsTLSv1 extends InsecureContextConfiguration { + AllowsTLSv1() { this = library + "AllowsTLSv1" } + + override string flag() { result = "TLSv1" } +} + +class AllowsTLSv1_1 extends InsecureContextConfiguration { + AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" } + + override string flag() { result = "TLSv1_1" } +} + +predicate unsafe_connection_creation(DataFlow::Node node, ProtocolVersion insecure_version) { + exists(AllowsTLSv1 c | c.hasFlowTo(node)) and + insecure_version = "TLSv1" + or + exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and + insecure_version = "TLSv1_1" + or + exists(TlsLibrary l | l.insecure_connection_creation(insecure_version) = node) +} + +predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) { + exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) | + cc = node + ) +} diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql index 81b3558907e..b873e5f3ce8 100644 --- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql +++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql @@ -10,277 +10,13 @@ */ import python -import semmle.python.ApiGraphs +import FluentApiModel +// string foo(ProtocolRestriction r) { result = r.getRestriction() } // The idea is to track flow from the creation of an insecure context to a use // such as `wrap_socket`. There should be a data-flow path for each insecure version // and each path should have a version specific sanitizer. This will allow fluent api // style code to block the paths one by one. -// -// class InsecureContextCreation extends DataFlow::CfgNode { -// override CallNode node; -// InsecureContextCreation() { -// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and -// insecure_version().asCfgNode() in [node.getArg(0), node.getArgByName("protocol")] -// } -// } -// class InsecureSSLContextCreation extends DataFlow::CfgNode { -// override CallNode node; -// InsecureSSLContextCreation() { -// this = API::moduleImport("ssl").getMember("create_default_context").getACall() -// or -// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and -// API::moduleImport("ssl").getMember("PROTOCOL_TLS").getAUse().asCfgNode() in [ -// node.getArg(0), node.getArgByName("protocol") -// ] -// } -// } -abstract class ContextCreation extends DataFlow::CfgNode { - abstract DataFlow::CfgNode getProtocol(); -} - -abstract class ConnectionCreation extends DataFlow::CfgNode { - abstract DataFlow::CfgNode getContext(); -} - -class ProtocolRestriction extends DataFlow::CfgNode { - abstract DataFlow::CfgNode getContext(); - - abstract string getRestriction(); -} - -abstract class TlsLibrary extends string { - TlsLibrary() { this in ["ssl", "pyOpenSSL"] } - - abstract string specific_insecure_version_name(); - - abstract string unspecific_version_name(); - - abstract API::Node version_constants(); - - DataFlow::Node insecure_version() { - result = version_constants().getMember(specific_insecure_version_name()).getAUse() - } - - DataFlow::Node unspecific_version() { - result = version_constants().getMember(unspecific_version_name()).getAUse() - } - - abstract DataFlow::CfgNode default_context_creation(); - - abstract ContextCreation specific_context_creation(); - - ContextCreation insecure_context_creation() { - result = specific_context_creation() and - result.getProtocol() = insecure_version() - } - - DataFlow::CfgNode unspecific_context_creation() { - result = default_context_creation() - or - result = specific_context_creation() and - result.(ContextCreation).getProtocol() = unspecific_version() - } - - /** A connection is created in an outright insecure manner. */ - abstract DataFlow::CfgNode insecure_connection_creation(); - - /** A connection is created from a context. */ - abstract ConnectionCreation connection_creation(); - - abstract ProtocolRestriction protocol_restriction(); -} - -module ssl { - class SSLContextCreation extends ContextCreation { - override CallNode node; - - SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() } - - override DataFlow::CfgNode getProtocol() { - result.getNode() in [node.getArg(0), node.getArgByName("protocol")] - } - } - - class WrapSocketCall extends ConnectionCreation { - override CallNode node; - - WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" } - - override DataFlow::CfgNode getContext() { - result.getNode() = node.getFunction().(AttrNode).getObject() - } - } - - class OptionsAugOr extends ProtocolRestriction { - string restriction; - - OptionsAugOr() { - exists(AugAssign aa, AttrNode attr | - aa.getOperation().getOp() instanceof BitOr and - aa.getTarget() = attr.getNode() and - attr.getName() = "options" and - attr.getObject() = node and - aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr() - ) - } - - override DataFlow::CfgNode getContext() { result = this } - - override string getRestriction() { result = restriction } - } - - class Ssl extends TlsLibrary { - Ssl() { this = "ssl" } - - override string specific_insecure_version_name() { - result in [ - "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1", - "PROTOCOL_TLSv1_1" - ] - } - - override string unspecific_version_name() { result = "PROTOCOL_TLS" } - - override API::Node version_constants() { result = API::moduleImport("ssl") } - - override DataFlow::CfgNode default_context_creation() { - result = API::moduleImport("ssl").getMember("create_default_context").getACall() - } - - override ContextCreation specific_context_creation() { result instanceof SSLContextCreation } - - override DataFlow::CfgNode insecure_connection_creation() { - result = API::moduleImport("ssl").getMember("wrap_socket").getACall() - } - - override ConnectionCreation connection_creation() { result instanceof WrapSocketCall } - - override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr } - } -} - -module pyOpenSSL { - class PyOpenSSLContextCreation extends ContextCreation { - override CallNode node; - - PyOpenSSLContextCreation() { - this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall() - } - - override DataFlow::CfgNode getProtocol() { - result.getNode() in [node.getArg(0), node.getArgByName("method")] - } - } - - class ConnectionCall extends ConnectionCreation { - override CallNode node; - - ConnectionCall() { - this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall() - } - - override DataFlow::CfgNode getContext() { - result.getNode() in [node.getArg(0), node.getArgByName("context")] - } - } - - class SetOptionsCall extends ProtocolRestriction { - override CallNode node; - - SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" } - - override DataFlow::CfgNode getContext() { - result.getNode() = node.getFunction().(AttrNode).getObject() - } - - override string getRestriction() { - API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [ - node.getArg(0), node.getArgByName("options") - ] - } - } - - class PyOpenSSL extends TlsLibrary { - PyOpenSSL() { this = "pyOpenSSL" } - - override string specific_insecure_version_name() { - result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"] - } - - override string unspecific_version_name() { result = "TLS_METHOD" } - - override API::Node version_constants() { - result = API::moduleImport("pyOpenSSL").getMember("SSL") - } - - override DataFlow::CfgNode default_context_creation() { none() } - - override ContextCreation specific_context_creation() { - result instanceof PyOpenSSLContextCreation - } - - override DataFlow::CfgNode insecure_connection_creation() { none() } - - override ConnectionCreation connection_creation() { result instanceof ConnectionCall } - - override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall } - } -} - -class InsecureContextConfiguration extends DataFlow::Configuration { - TlsLibrary library; - - InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] } - - override predicate isSource(DataFlow::Node source) { - source = library.unspecific_context_creation() - } - - override predicate isSink(DataFlow::Node sink) { - sink = library.connection_creation().getContext() - } - - abstract string flag(); - - override predicate isBarrierOut(DataFlow::Node node) { - exists(ProtocolRestriction r | - r = library.protocol_restriction() and - node = r.getContext() and - r.getRestriction() = flag() - ) - } -} - -class AllowsTLSv1 extends InsecureContextConfiguration { - AllowsTLSv1() { this = library + "AllowsTLSv1" } - - override string flag() { result = "OP_NO_TLSv1" } -} - -class AllowsTLSv1_1 extends InsecureContextConfiguration { - AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" } - - override string flag() { result = "OP_NO_TLSv1_1" } -} - -predicate unsafe_connection_creation(DataFlow::Node node, string insecure_version) { - exists(AllowsTLSv1 c | c.hasFlowTo(node)) and - insecure_version = "TLSv1" - or - exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and - insecure_version = "TLSv1" - or - exists(TlsLibrary l | l.insecure_connection_creation() = node) and - insecure_version = "[multiple]" -} - -predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) { - exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation() | - cc = node and insecure_version = cc.getProtocol().toString() - ) -} - from DataFlow::Node node, string insecure_version where unsafe_connection_creation(node, insecure_version) diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll new file mode 100644 index 00000000000..b9a68648f93 --- /dev/null +++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll @@ -0,0 +1,73 @@ +import python +import semmle.python.ApiGraphs +import TlsLibraryModel + +class PyOpenSSLContextCreation extends ContextCreation { + override CallNode node; + + PyOpenSSLContextCreation() { + this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Context").getACall() + } + + override DataFlow::CfgNode getProtocol() { + result.getNode() in [node.getArg(0), node.getArgByName("method")] + } +} + +class ConnectionCall extends ConnectionCreation { + override CallNode node; + + ConnectionCall() { + this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Connection").getACall() + } + + override DataFlow::CfgNode getContext() { + result.getNode() in [node.getArg(0), node.getArgByName("context")] + } +} + +class SetOptionsCall extends ProtocolRestriction { + override CallNode node; + + 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 PyOpenSSL extends TlsLibrary { + PyOpenSSL() { this = "pyOpenSSL" } + + override string specific_insecure_version_name(ProtocolVersion version) { + version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and + result = version + "_METHOD" + } + + override string unspecific_version_name() { + result in [ + "TLS_METHOD", // This is not actually available in pyOpenSSL yet + "SSLv23_METHOD" // This is what can negotiate TLS 1.3 (indeed, I know, I did test that..) + ] + } + + override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") } + + override DataFlow::CfgNode default_context_creation() { none() } + + override ContextCreation specific_context_creation() { + result instanceof PyOpenSSLContextCreation + } + + override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { none() } + + override ConnectionCreation connection_creation() { result instanceof ConnectionCall } + + override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall } +} diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll new file mode 100644 index 00000000000..369149fd638 --- /dev/null +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -0,0 +1,106 @@ +import python +import semmle.python.ApiGraphs +import semmle.python.dataflow.new.internal.Attributes as Attributes +import TlsLibraryModel + +class SSLContextCreation extends ContextCreation { + override CallNode node; + + SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() } + + override DataFlow::CfgNode getProtocol() { + result.getNode() in [node.getArg(0), node.getArgByName("protocol")] + } +} + +class WrapSocketCall extends ConnectionCreation { + override CallNode node; + + WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" } + + override DataFlow::CfgNode getContext() { + result.getNode() = node.getFunction().(AttrNode).getObject() + } +} + +class OptionsAugOr extends ProtocolRestriction { + ProtocolVersion restriction; + + OptionsAugOr() { + exists(AugAssign aa, AttrNode attr | + aa.getOperation().getOp() instanceof BitOr and + aa.getTarget() = attr.getNode() and + attr.getName() = "options" and + attr.getObject() = node and + API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() in [ + aa.getValue(), aa.getValue().getAChildNode() + ] + ) + } + + override DataFlow::CfgNode getContext() { result = this } + + override ProtocolVersion getRestriction() { result = restriction } +} + +class ContextSetVersion extends ProtocolRestriction { + string restriction; + + ContextSetVersion() { + exists(Attributes::AttrWrite aw | + aw.getObject().asCfgNode() = node and + aw.getAttributeName() = "minimum_version" and + aw.getValue() = + API::moduleImport("ssl").getMember("TLSVersion").getMember(restriction).getAUse() + ) + } + + override DataFlow::CfgNode getContext() { result = this } + + override ProtocolVersion getRestriction() { result.lessThan(restriction) } +} + +class Ssl extends TlsLibrary { + Ssl() { this = "ssl" } + + override string specific_insecure_version_name(ProtocolVersion version) { + version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and + result = "PROTOCOL_" + version + // result in ["PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"] + } + + override string unspecific_version_name() { + result = + "PROTOCOL_" + + [ + "TLS", + // This can negotiate a TLS 1.3 connection (!) + // see https://docs.python.org/3/library/ssl.html#ssl-contexts + "SSLv23" + ] + } + + override API::Node version_constants() { result = API::moduleImport("ssl") } + + override DataFlow::CfgNode default_context_creation() { + result = API::moduleImport("ssl").getMember("create_default_context").getACall() //and + // see https://docs.python.org/3/library/ssl.html#context-creation + // version in ["TLSv1", "TLSv1_1"] + } + + override ContextCreation specific_context_creation() { result instanceof SSLContextCreation } + + override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { + result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and + insecure_version(version).asCfgNode() = + result.asCfgNode().(CallNode).getArgByName("ssl_version") + } + + override ConnectionCreation connection_creation() { result instanceof WrapSocketCall } + + override ProtocolRestriction protocol_restriction() { + result instanceof OptionsAugOr + or + result instanceof ContextSetVersion + } +} diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll new file mode 100644 index 00000000000..41db81ad1c8 --- /dev/null +++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll @@ -0,0 +1,80 @@ +import python +import semmle.python.ApiGraphs +import Ssl +import PyOpenSSL + +/** + * A specific protocol version. + * We use this to identify a protocol. + */ +class ProtocolVersion extends string { + ProtocolVersion() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] } + + 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" + } +} + +abstract class ContextCreation extends DataFlow::CfgNode { + abstract DataFlow::CfgNode getProtocol(); +} + +abstract class ConnectionCreation extends DataFlow::CfgNode { + abstract DataFlow::CfgNode getContext(); +} + +abstract class ProtocolRestriction extends DataFlow::CfgNode { + abstract DataFlow::CfgNode getContext(); + + abstract ProtocolVersion getRestriction(); +} + +abstract class TlsLibrary extends string { + TlsLibrary() { this in ["ssl", "pyOpenSSL"] } + + /** The name of a specific protocol version, known to be insecure. */ + abstract string specific_insecure_version_name(ProtocolVersion version); + + /** The name of an unspecific protocol version, say TLS, known to have insecure insatnces. */ + abstract string unspecific_version_name(); + + abstract API::Node version_constants(); + + DataFlow::Node insecure_version(ProtocolVersion version) { + result = version_constants().getMember(specific_insecure_version_name(version)).getAUse() + } + + DataFlow::Node unspecific_version() { + result = version_constants().getMember(unspecific_version_name()).getAUse() + } + + abstract DataFlow::CfgNode default_context_creation(); + + abstract ContextCreation specific_context_creation(); + + ContextCreation insecure_context_creation(ProtocolVersion version) { + result = specific_context_creation() and + result.getProtocol() = insecure_version(version) + } + + DataFlow::CfgNode unspecific_context_creation() { + result = default_context_creation() + or + result = specific_context_creation() and + result.(ContextCreation).getProtocol() = unspecific_version() + } + + /** A connection is created in an outright insecure manner. */ + abstract DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version); + + /** A connection is created from a context. */ + abstract ConnectionCreation connection_creation(); + + abstract ProtocolRestriction protocol_restriction(); +}