|
10 | 10 | */
|
11 | 11 |
|
12 | 12 | import python
|
| 13 | +import semmle.python.dataflow.new.DataFlow |
| 14 | +import FluentApiModel |
13 | 15 |
|
14 |
| -private ModuleValue the_ssl_module() { result = Module::named("ssl") } |
15 |
| - |
16 |
| -FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") } |
17 |
| - |
18 |
| -ClassValue ssl_Context_class() { result = the_ssl_module().attr("SSLContext") } |
| 16 | +// Helper for pretty printer `configName`. |
| 17 | +// This is a consequence of missing pretty priting. |
| 18 | +// We do not want to evaluate our bespoke pretty printer |
| 19 | +// for all `DataFlow::Node`s so we define a sub class of interesting ones. |
| 20 | +class ProtocolConfiguration extends DataFlow::Node { |
| 21 | + ProtocolConfiguration() { |
| 22 | + unsafe_connection_creation_with_context(_, _, this, _) |
| 23 | + or |
| 24 | + unsafe_connection_creation_without_context(this, _) |
| 25 | + or |
| 26 | + unsafe_context_creation(this, _) |
| 27 | + } |
19 | 28 |
|
20 |
| -private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SSL") } |
| 29 | + AstNode getNode() { result = this.asCfgNode().(CallNode).getFunction().getNode() } |
| 30 | +} |
21 | 31 |
|
22 |
| -ClassValue the_pyOpenSSL_Context_class() { result = Value::named("pyOpenSSL.SSL.Context") } |
| 32 | +// Helper for pretty printer `callName`. |
| 33 | +// This is a consequence of missing pretty priting. |
| 34 | +// We do not want to evaluate our bespoke pretty printer |
| 35 | +// for all `AstNode`s so we define a sub class of interesting ones. |
| 36 | +// |
| 37 | +// Note that AstNode is abstract and AstNode_ is a library class, so |
| 38 | +// we have to extend @py_ast_node. |
| 39 | +class Nameable extends @py_ast_node { |
| 40 | + Nameable() { |
| 41 | + this = any(ProtocolConfiguration pc).getNode() |
| 42 | + or |
| 43 | + exists(Nameable attr | this = attr.(Attribute).getObject()) |
| 44 | + } |
23 | 45 |
|
24 |
| -string insecure_version_name() { |
25 |
| - // For `pyOpenSSL.SSL` |
26 |
| - result = "SSLv2_METHOD" or |
27 |
| - result = "SSLv23_METHOD" or |
28 |
| - result = "SSLv3_METHOD" or |
29 |
| - result = "TLSv1_METHOD" or |
30 |
| - // For the `ssl` module |
31 |
| - result = "PROTOCOL_SSLv2" or |
32 |
| - result = "PROTOCOL_SSLv3" or |
33 |
| - result = "PROTOCOL_SSLv23" or |
34 |
| - result = "PROTOCOL_TLS" or |
35 |
| - result = "PROTOCOL_TLSv1" |
| 46 | + string toString() { result = "AstNode" } |
36 | 47 | }
|
37 | 48 |
|
38 |
| -/* |
39 |
| - * A syntactic check for cases where points-to analysis cannot infer the presence of |
40 |
| - * a protocol constant, e.g. if it has been removed in later versions of the `ssl` |
41 |
| - * library. |
42 |
| - */ |
43 |
| - |
44 |
| -bindingset[named_argument] |
45 |
| -predicate probable_insecure_ssl_constant( |
46 |
| - CallNode call, string insecure_version, string named_argument |
47 |
| -) { |
48 |
| - exists(ControlFlowNode arg | |
49 |
| - arg = call.getArgByName(named_argument) or |
50 |
| - arg = call.getArg(0) |
51 |
| - | |
52 |
| - arg.(AttrNode).getObject(insecure_version).pointsTo(the_ssl_module()) |
53 |
| - or |
54 |
| - arg.(NameNode).getId() = insecure_version and |
55 |
| - exists(Import imp | |
56 |
| - imp.getAnImportedModuleName() = "ssl" and |
57 |
| - imp.getAName().getAsname().(Name).getId() = insecure_version |
58 |
| - ) |
59 |
| - ) |
| 49 | +string callName(Nameable call) { |
| 50 | + result = call.(Name).getId() |
| 51 | + or |
| 52 | + exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName()) |
60 | 53 | }
|
61 | 54 |
|
62 |
| -predicate unsafe_ssl_wrap_socket_call( |
63 |
| - CallNode call, string method_name, string insecure_version, string named_argument |
64 |
| -) { |
65 |
| - ( |
66 |
| - call = ssl_wrap_socket().getACall() and |
67 |
| - method_name = "deprecated method ssl.wrap_socket" and |
68 |
| - named_argument = "ssl_version" |
69 |
| - or |
70 |
| - call = ssl_Context_class().getACall() and |
71 |
| - named_argument = "protocol" and |
72 |
| - method_name = "ssl.SSLContext" |
73 |
| - ) and |
74 |
| - insecure_version = insecure_version_name() and |
75 |
| - ( |
76 |
| - call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version)) |
77 |
| - or |
78 |
| - probable_insecure_ssl_constant(call, insecure_version, named_argument) |
79 |
| - ) |
| 55 | +string configName(ProtocolConfiguration protocolConfiguration) { |
| 56 | + result = |
| 57 | + "call to " + callName(protocolConfiguration.asCfgNode().(CallNode).getFunction().getNode()) |
| 58 | + or |
| 59 | + not protocolConfiguration.asCfgNode() instanceof CallNode and |
| 60 | + not protocolConfiguration instanceof ContextCreation and |
| 61 | + result = "context modification" |
80 | 62 | }
|
81 | 63 |
|
82 |
| -predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) { |
83 |
| - call = the_pyOpenSSL_Context_class().getACall() and |
84 |
| - insecure_version = insecure_version_name() and |
85 |
| - call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version)) |
| 64 | +string verb(boolean specific) { |
| 65 | + specific = true and result = "specified" |
| 66 | + or |
| 67 | + specific = false and result = "allowed" |
86 | 68 | }
|
87 | 69 |
|
88 |
| -from CallNode call, string method_name, string insecure_version |
| 70 | +from |
| 71 | + DataFlow::Node connectionCreation, string insecure_version, DataFlow::Node protocolConfiguration, |
| 72 | + boolean specific |
89 | 73 | where
|
90 |
| - unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _) |
| 74 | + unsafe_connection_creation_with_context(connectionCreation, insecure_version, |
| 75 | + protocolConfiguration, specific) |
| 76 | + or |
| 77 | + unsafe_connection_creation_without_context(connectionCreation, insecure_version) and |
| 78 | + protocolConfiguration = connectionCreation and |
| 79 | + specific = true |
91 | 80 | or
|
92 |
| - unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context" |
93 |
| -select call, |
94 |
| - "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name + |
95 |
| - "." |
| 81 | + unsafe_context_creation(protocolConfiguration, insecure_version) and |
| 82 | + connectionCreation = protocolConfiguration and |
| 83 | + specific = true |
| 84 | +select connectionCreation, |
| 85 | + "Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ", |
| 86 | + protocolConfiguration, configName(protocolConfiguration) |
0 commit comments