|
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