|
10 | 10 | * @security-severity 7.5 |
11 | 11 | * @precision high |
12 | 12 | * @tags security |
| 13 | + * experimental |
13 | 14 | * external/cwe/cwe-022 |
14 | 15 | */ |
15 | 16 |
|
16 | 17 | import python |
17 | | -import semmle.python.Concepts |
18 | | -import semmle.python.dataflow.new.internal.DataFlowPublic |
19 | | -import semmle.python.ApiGraphs |
| 18 | +import UnsafeUnpackQuery |
20 | 19 | import DataFlow::PathGraph |
21 | | -import semmle.python.dataflow.new.TaintTracking |
22 | | -import semmle.python.frameworks.Stdlib |
23 | | - |
24 | | -class UnsafeUnpackingConfig extends TaintTracking::Configuration { |
25 | | - UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" } |
26 | | - |
27 | | - override predicate isSource(DataFlow::Node source) { |
28 | | - // A source coming from a remote location |
29 | | - exists(Http::Client::Request request | source = request) |
30 | | - or |
31 | | - // A source coming from a CLI argparse module |
32 | | - // see argparse: https://docs.python.org/3/library/argparse.html |
33 | | - exists(MethodCallNode args | |
34 | | - args = source.(AttrRead).getObject().getALocalSource() and |
35 | | - args = |
36 | | - API::moduleImport("argparse") |
37 | | - .getMember("ArgumentParser") |
38 | | - .getACall() |
39 | | - .getReturn() |
40 | | - .getMember("parse_args") |
41 | | - .getACall() |
42 | | - ) |
43 | | - or |
44 | | - // A source catching an S3 filename download |
45 | | - // see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file |
46 | | - exists(MethodCallNode mcn, Node s3, Node bc | |
47 | | - bc = API::moduleImport("boto3").getMember("client").getACall() and |
48 | | - bc = s3.getALocalSource() and |
49 | | - mcn.calls(s3, "download_file") and |
50 | | - source = mcn.getArg(2) |
51 | | - ) |
52 | | - or |
53 | | - // A source download a file using wget |
54 | | - // see wget: https://pypi.org/project/wget/ |
55 | | - exists(API::CallNode mcn | |
56 | | - mcn = API::moduleImport("wget").getMember("download").getACall() and |
57 | | - ( |
58 | | - source = mcn.getArg(1) |
59 | | - or |
60 | | - source = mcn.getReturn().asSource() and not exists(Node arg | arg = mcn.getArg(1)) |
61 | | - ) |
62 | | - ) |
63 | | - or |
64 | | - // catch the uploaded files as a source |
65 | | - exists(Subscript s, Attribute at | |
66 | | - at = s.getObject() and at.getAttr() = "FILES" and source.asExpr() = s |
67 | | - ) |
68 | | - or |
69 | | - exists(Node obj, AttrRead ar | |
70 | | - ar.getAMethodCall("get").flowsTo(source) and |
71 | | - ar.accesses(obj, "FILES") |
72 | | - ) |
73 | | - or |
74 | | - exists(Node obj, AttrRead ar | |
75 | | - ar.getAMethodCall("getlist").flowsTo(source) and |
76 | | - ar.accesses(obj, "FILES") |
77 | | - ) |
78 | | - } |
79 | | - |
80 | | - override predicate isSink(DataFlow::Node sink) { |
81 | | - // A sink capturing method calls to `unpack_archive`. |
82 | | - sink = API::moduleImport("shutil").getMember("unpack_archive").getACall().getArg(0) |
83 | | - } |
84 | | - |
85 | | - override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { |
86 | | - // Writing the response data to the archive |
87 | | - exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc | |
88 | | - is.flowsTo(f) and |
89 | | - mc.calls(f, "write") and |
90 | | - nodeFrom = mc.getArg(0) and |
91 | | - nodeTo = is.(CallCfgNode).getArg(0) |
92 | | - ) |
93 | | - or |
94 | | - // Copying the response data to the archive |
95 | | - exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc | |
96 | | - is.flowsTo(f) and |
97 | | - mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and |
98 | | - f = mc.getArg(1) and |
99 | | - nodeFrom = mc.getArg(0) and |
100 | | - nodeTo = is.(CallCfgNode).getArg(0) |
101 | | - ) |
102 | | - or |
103 | | - // Reading the response |
104 | | - exists(MethodCallNode mc | |
105 | | - nodeFrom = mc.getObject() and |
106 | | - mc.getMethodName() = "read" and |
107 | | - mc.flowsTo(nodeTo) |
108 | | - ) |
109 | | - or |
110 | | - // Accessing the name or raw content |
111 | | - exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo)) |
112 | | - or |
113 | | - //Use of join of filename |
114 | | - exists(API::CallNode mcn | |
115 | | - mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and |
116 | | - nodeFrom = mcn.getArg(1) and |
117 | | - mcn.flowsTo(nodeTo) |
118 | | - ) |
119 | | - or |
120 | | - // Read by chunks |
121 | | - exists(MethodCallNode mc | |
122 | | - nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo) |
123 | | - ) |
124 | | - or |
125 | | - // Considering the use of closing() |
126 | | - exists(API::CallNode closing | |
127 | | - closing = API::moduleImport("contextlib").getMember("closing").getACall() and |
128 | | - closing.flowsTo(nodeTo) and |
129 | | - nodeFrom = closing.getArg(0) |
130 | | - ) |
131 | | - or |
132 | | - // Considering the use of "fs" |
133 | | - exists(API::CallNode fs, MethodCallNode mcn | |
134 | | - fs = |
135 | | - API::moduleImport("django") |
136 | | - .getMember("core") |
137 | | - .getMember("files") |
138 | | - .getMember("storage") |
139 | | - .getMember("FileSystemStorage") |
140 | | - .getACall() and |
141 | | - fs.flowsTo(mcn.getObject()) and |
142 | | - mcn.getMethodName() = ["save", "path"] and |
143 | | - nodeFrom = mcn.getArg(0) and |
144 | | - nodeTo = mcn |
145 | | - ) |
146 | | - } |
147 | | -} |
148 | 20 |
|
149 | 21 | from UnsafeUnpackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink |
150 | 22 | where config.hasFlowPath(source, sink) |
|
0 commit comments