From 68fd75ca34dd9d792cdce7f400a72acd8b473b54 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Mon, 5 Dec 2022 17:20:22 +0100
Subject: [PATCH 01/52] UnpackUnsafe query and tests
---
.../Security/CWE-022bis/UnsafeUnpack.qhelp | 56 +++++++++++++++++++
.../Security/CWE-022bis/UnsafeUnpack.ql | 56 +++++++++++++++++++
.../CWE-022bis/examples/HIT_UnsafeUnpack.py | 12 ++++
.../CWE-022bis/examples/NoHIT_UnsafeUnpack.py | 17 ++++++
.../Security/CWE-022/UnsafeUnpack.expected | 10 ++++
.../Security/CWE-022/UnsafeUnpack.py | 12 ++++
.../Security/CWE-022/UnsafeUnpack.qlref | 1 +
7 files changed, 164 insertions(+)
create mode 100644 python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
create mode 100644 python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
create mode 100644 python/ql/src/experimental/Security/CWE-022bis/examples/HIT_UnsafeUnpack.py
create mode 100644 python/ql/src/experimental/Security/CWE-022bis/examples/NoHIT_UnsafeUnpack.py
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.qlref
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
new file mode 100644
index 000000000000..c1115e819b95
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
@@ -0,0 +1,56 @@
+
+
+
+
+Extracting files from a malicious tarball without validating that the destination file path
+is within the destination directory using `shutil.unpack_archive()` can cause files outside the
+destination directory to be overwritten, due to the possible presence of directory traversal elements
+ (..) in archive path names.
+
+Tarball contain archive entries representing each file in the archive. These entries
+include a file path for the entry, but these file paths are not restricted and may contain
+unexpected special elements such as the directory traversal element (..). If these
+file paths are used to determine an output file to write the contents of the archive item to, then
+the file may be written to an unexpected location. This can result in sensitive information being
+revealed or deleted, or an attacker being able to influence behavior by modifying unexpected
+files.
+
+For example, if a tarball contains a file entry ../sim4n6.txt, and the tarball
+is extracted to the directory /tmp/tmp123, then naively combining the paths would result
+in an output file path of /tmp/tmp123/../sim4n6.txt, which would cause the file to be
+written to /tmp/.
+
+
+
+
+Ensure that output paths constructed from tarball entries are validated
+to prevent writing files to unexpected locations.
+
+Consider using a safer module, such as: zipfile
+
+
+
+
+
+In this example an archive is extracted without validating file paths.
+
+
+
+
+To fix this vulnerability, we need to call the function tarfile.extract()
+ on each member after verifying that it does not contain either `..` or startswith `/`.
+
+
+
+
+
+
+
+
+ Shutil official documentation
+ shutil.unpack_archive() warning.
+
+
+
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
new file mode 100644
index 000000000000..eb78b13cd0ac
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -0,0 +1,56 @@
+/**
+ * @name Arbitrary file write during a remotely downloaded tarball extraction
+ * @description Extracting files from a malicious tarball using `shutil.unpack_archive()` without validating
+ * that the destination file path is within the destination directory can cause files outside
+ * the destination directory to be overwritten. More precisely, if the tarball comes from a remote location.
+ * @kind path-problem
+ * @id py/unsafe-unpacking
+ * @problem.severity error
+ * @security-severity 7.5
+ * @precision high
+ * @tags security
+ * external/cwe/cwe-022bis
+ */
+
+import python
+import experimental.semmle.python.Concepts
+import DataFlow::PathGraph
+import semmle.python.ApiGraphs
+import semmle.python.dataflow.new.internal.Attributes
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.ApiGraphs
+import semmle.python.dataflow.new.TaintTracking
+import semmle.python.Concepts
+
+class UnsafeUnpackingConfig extends TaintTracking::Configuration {
+ UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" }
+
+ override predicate isSource(DataFlow::Node source) {
+ // A source coming from a remote location
+ exists(Http::Client::Request request | source = request) and
+ not source.getScope().getLocation().getFile().inStdlib()
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ // A sink capturing method calls to `unpack_archive`.
+ sink =
+ API::moduleImport("shutil").getMember("unpack_archive").getACall().getParameter(0).asSink() and
+ not sink.getScope().getLocation().getFile().inStdlib()
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ // Writing the response data to the archive
+ exists(API::CallNode call, MethodCallNode w |
+ nodeTo = call.getArg(0) and
+ call = API::builtin("open").getACall() and
+ w.getMethodName() = "write" and
+ w.getObject() = call.getReturn().getAValueReachableFromSource() and
+ nodeFrom = w.getArg(0)
+ )
+ }
+}
+
+from UnsafeUnpackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select source.getNode(), source, sink, "Unsafe extraction from a malicious tarball, is used in a $@",
+ source.getAQlClass(), "during archive unpacking."
\ No newline at end of file
diff --git a/python/ql/src/experimental/Security/CWE-022bis/examples/HIT_UnsafeUnpack.py b/python/ql/src/experimental/Security/CWE-022bis/examples/HIT_UnsafeUnpack.py
new file mode 100644
index 000000000000..cc0f857f8c07
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-022bis/examples/HIT_UnsafeUnpack.py
@@ -0,0 +1,12 @@
+import requests
+import shutil
+
+url = "https://www.someremote.location/tarball.tar.gz"
+response = requests.get(url, stream=True)
+
+tarpath = "/tmp/tmp456/tarball.tar.gz"
+with open(tarpath, "wb") as f:
+ f.write(response.raw.read())
+
+untarredpath = "/tmp/tmp123"
+shutil.unpack_archive(tarpath, untarredpath)
\ No newline at end of file
diff --git a/python/ql/src/experimental/Security/CWE-022bis/examples/NoHIT_UnsafeUnpack.py b/python/ql/src/experimental/Security/CWE-022bis/examples/NoHIT_UnsafeUnpack.py
new file mode 100644
index 000000000000..426bcd714817
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-022bis/examples/NoHIT_UnsafeUnpack.py
@@ -0,0 +1,17 @@
+import requests
+import tarfile
+
+url = "https://www.someremote.location/tarball.tar.gz"
+response = requests.get(url, stream=True)
+
+tarpath = "/tmp/tmp456/tarball.tar.gz"
+with open(tarpath, "wb") as f:
+ f.write(response.raw.read())
+
+untarredpath = "/tmp/tmp123"
+with tarfile.open(tarpath) as tar:
+ for member in tar.getmembers():
+ if member.name.startswith("/") or ".." in member.name:
+ raise Exception("Path traversal identified in tarball")
+
+ tar.extract(untarredpath, member)
\ No newline at end of file
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
new file mode 100644
index 000000000000..96d25c65a30a
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -0,0 +1,10 @@
+edges
+| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath |
+nodes
+| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
+subpaths
+#select
+| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball, is used in a $@ | PathNode | during archive unpacking. |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
new file mode 100644
index 000000000000..cc0f857f8c07
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -0,0 +1,12 @@
+import requests
+import shutil
+
+url = "https://www.someremote.location/tarball.tar.gz"
+response = requests.get(url, stream=True)
+
+tarpath = "/tmp/tmp456/tarball.tar.gz"
+with open(tarpath, "wb") as f:
+ f.write(response.raw.read())
+
+untarredpath = "/tmp/tmp123"
+shutil.unpack_archive(tarpath, untarredpath)
\ No newline at end of file
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.qlref b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.qlref
new file mode 100644
index 000000000000..90e5db651a0d
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE-022bis/UnsafeUnpack.ql
\ No newline at end of file
From 054c06be6579ef2338f900df58539a29c8a3fc75 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Tue, 6 Dec 2022 02:51:07 +0100
Subject: [PATCH 02/52] Update UnsafeUnpack.ql
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 43 +++++++++++--------
1 file changed, 24 insertions(+), 19 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index eb78b13cd0ac..c80dda11cea0 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -8,49 +8,54 @@
* @problem.severity error
* @security-severity 7.5
* @precision high
- * @tags security
+ * @tags securityimport semmle.python.dataflow.TaintTracking
* external/cwe/cwe-022bis
*/
import python
-import experimental.semmle.python.Concepts
-import DataFlow::PathGraph
-import semmle.python.ApiGraphs
-import semmle.python.dataflow.new.internal.Attributes
-import semmle.python.dataflow.new.DataFlow
+import semmle.python.Concepts
+import semmle.python.dataflow.new.internal.DataFlowPublic
import semmle.python.ApiGraphs
+import DataFlow::PathGraph
import semmle.python.dataflow.new.TaintTracking
-import semmle.python.Concepts
class UnsafeUnpackingConfig extends TaintTracking::Configuration {
UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" }
override predicate isSource(DataFlow::Node source) {
// A source coming from a remote location
- exists(Http::Client::Request request | source = request) and
- not source.getScope().getLocation().getFile().inStdlib()
+ exists(Http::Client::Request request | source = request)
}
override predicate isSink(DataFlow::Node sink) {
// A sink capturing method calls to `unpack_archive`.
- sink =
- API::moduleImport("shutil").getMember("unpack_archive").getACall().getParameter(0).asSink() and
- not sink.getScope().getLocation().getFile().inStdlib()
+ sink = API::moduleImport("shutil").getMember("unpack_archive").getACall().getArg(0)
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Writing the response data to the archive
- exists(API::CallNode call, MethodCallNode w |
- nodeTo = call.getArg(0) and
+ (exists(API::CallNode call, MethodCallNode mc, Node f |
+ mc.getMethodName() = "write" and
+ f = mc.getObject() and
+ nodeTo = mc.getArg(0) and
call = API::builtin("open").getACall() and
- w.getMethodName() = "write" and
- w.getObject() = call.getReturn().getAValueReachableFromSource() and
- nodeFrom = w.getArg(0)
+ call.flowsTo(f) and
+ nodeFrom = call.getArg(0)
+ )
+ or
+ // Reading the response
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and
+ mc.getMethodName() = "read" and
+ nodeTo = mc
)
+ or
+ // Accessing the name
+ exists(AttrRead ar | ar.accesses(nodeFrom, "name") and nodeTo = ar))
}
}
from UnsafeUnpackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
-select source.getNode(), source, sink, "Unsafe extraction from a malicious tarball, is used in a $@",
- source.getAQlClass(), "during archive unpacking."
\ No newline at end of file
+select sink.getNode(), source, sink,
+ "Unsafe extraction from a malicious tarball retrieved from a remote location."
From a5849eb9b0cd434a14dfb849adad7c7ca194ba52 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Tue, 6 Dec 2022 14:00:08 +0100
Subject: [PATCH 03/52] Improved the additional taint step using InstanceSource
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 36 ++++++++++---------
1 file changed, 19 insertions(+), 17 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index c80dda11cea0..bd3d17ff71b8 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -18,6 +18,7 @@ import semmle.python.dataflow.new.internal.DataFlowPublic
import semmle.python.ApiGraphs
import DataFlow::PathGraph
import semmle.python.dataflow.new.TaintTracking
+import semmle.python.frameworks.Stdlib
class UnsafeUnpackingConfig extends TaintTracking::Configuration {
UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" }
@@ -34,24 +35,25 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Writing the response data to the archive
- (exists(API::CallNode call, MethodCallNode mc, Node f |
- mc.getMethodName() = "write" and
- f = mc.getObject() and
- nodeTo = mc.getArg(0) and
- call = API::builtin("open").getACall() and
- call.flowsTo(f) and
- nodeFrom = call.getArg(0)
+ (
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc.getMethodName() = "write" and
+ f = mc.getObject() and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
+ )
+ or
+ // Reading the response
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and
+ mc.getMethodName() = "read" and
+ nodeTo = mc
+ )
+ or
+ // Accessing the name
+ exists(AttrRead ar | ar.accesses(nodeFrom, "name") and nodeTo = ar)
)
- or
- // Reading the response
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and
- mc.getMethodName() = "read" and
- nodeTo = mc
- )
- or
- // Accessing the name
- exists(AttrRead ar | ar.accesses(nodeFrom, "name") and nodeTo = ar))
}
}
From c22c0b502940478b5434eab1c43895a691c22f59 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Tue, 6 Dec 2022 14:39:16 +0100
Subject: [PATCH 04/52] Update
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com>
---
.../ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
index c1115e819b95..d1fd1f4f414a 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
@@ -40,7 +40,7 @@ In this example an archive is extracted without validating file paths.
To fix this vulnerability, we need to call the function tarfile.extract()
- on each member after verifying that it does not contain either `..` or startswith `/`.
+ on each member after verifying that it does not contain either .. or startswith /.
From 9a60202de62da63e842b35eddbef838c4de08ca1 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Tue, 6 Dec 2022 14:40:35 +0100
Subject: [PATCH 05/52] Update
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com>
---
.../ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
index d1fd1f4f414a..cc42d88de1d7 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
@@ -5,7 +5,7 @@
Extracting files from a malicious tarball without validating that the destination file path
-is within the destination directory using `shutil.unpack_archive()` can cause files outside the
+is within the destination directory using shutil.unpack_archive() can cause files outside the
destination directory to be overwritten, due to the possible presence of directory traversal elements
(..) in archive path names.
From 58570b4d2c2b09dae1558b1f6bee2c77d0ffcdbb Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Tue, 6 Dec 2022 14:40:48 +0100
Subject: [PATCH 06/52] Update
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com>
---
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index bd3d17ff71b8..4c57e087dd30 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -8,7 +8,7 @@
* @problem.severity error
* @security-severity 7.5
* @precision high
- * @tags securityimport semmle.python.dataflow.TaintTracking
+ * @tags security
* external/cwe/cwe-022bis
*/
From 4896e62117c20dd2f5737a80f0e1cdd21401f8c4 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Tue, 6 Dec 2022 14:44:52 +0100
Subject: [PATCH 07/52] Use of more generic terms
---
.../src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
index cc42d88de1d7..1219bbe43bc8 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.qhelp
@@ -17,9 +17,9 @@ the file may be written to an unexpected location. This can result in sensitive
revealed or deleted, or an attacker being able to influence behavior by modifying unexpected
files.
-For example, if a tarball contains a file entry ../sim4n6.txt, and the tarball
+
For example, if a tarball contains a file entry ../sneaky-file.txt, and the tarball
is extracted to the directory /tmp/tmp123, then naively combining the paths would result
-in an output file path of /tmp/tmp123/../sim4n6.txt, which would cause the file to be
+in an output file path of /tmp/tmp123/../sneaky-file.txt, which would cause the file to be
written to /tmp/.
From 2801b8495aefcdcf7710bfcd5a5a3748c6da0145 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Tue, 6 Dec 2022 14:50:47 +0100
Subject: [PATCH 08/52] A fix of the tag name
---
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 4c57e087dd30..d550b2a4a379 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -9,7 +9,7 @@
* @security-severity 7.5
* @precision high
* @tags security
- * external/cwe/cwe-022bis
+ * external/cwe/cwe-022
*/
import python
From 9336f4f1a2db7d557f398a229985505870697a91 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Thu, 8 Dec 2022 12:26:59 +0100
Subject: [PATCH 09/52] Considering the use of contextlib.closing() method
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 11 ++++-
.../Security/CWE-022/UnsafeUnpack.expected | 6 ++-
.../Security/CWE-022/UnsafeUnpack.py | 46 ++++++++++++++++++-
3 files changed, 59 insertions(+), 4 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index d550b2a4a379..e793e6707718 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -34,8 +34,8 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
- // Writing the response data to the archive
(
+ // Writing the response data to the archive
exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
is.flowsTo(f) and
mc.getMethodName() = "write" and
@@ -48,11 +48,18 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
exists(MethodCallNode mc |
nodeFrom = mc.getObject() and
mc.getMethodName() = "read" and
- nodeTo = mc
+ mc.flowsTo(nodeTo)
)
or
// Accessing the name
exists(AttrRead ar | ar.accesses(nodeFrom, "name") and nodeTo = ar)
+ or
+ // Considering closing use
+ exists(API::Node closing |
+ closing = API::moduleImport("contextlib").getMember("closing") and
+ closing.getACall().flowsTo(nodeTo) and
+ nodeFrom = closing.getACall().getArg(0)
+ )
)
}
}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
index 96d25c65a30a..b1e93bf3ab28 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -1,10 +1,14 @@
edges
| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute |
| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path |
nodes
| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | semmle.label | ControlFlowNode for to_path |
subpaths
#select
-| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball, is used in a $@ | PathNode | during archive unpacking. |
+| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index cc0f857f8c07..c7820e52b04b 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -9,4 +9,48 @@
f.write(response.raw.read())
untarredpath = "/tmp/tmp123"
-shutil.unpack_archive(tarpath, untarredpath)
\ No newline at end of file
+shutil.unpack_archive(tarpath, untarredpath)
+
+
+import tempfile
+import os
+from urllib import request
+import contextlib
+import shutil
+
+unpack = True
+to_path = "/tmp/tmp123"
+uri = "https://www.goog.com/zzz.tar.gz"
+scheme = "https"
+
+with tempfile.TemporaryDirectory() as temp_dir:
+ if unpack and (str(uri).endswith("zip") or str(uri).endswith("tar.gz")):
+ unpack_path = to_path
+ to_path = temp_dir
+ else:
+ unpack_path = None
+ if scheme in ["http", "https", "ftp"]:
+ if os.path.isdir(to_path):
+ to_path = os.path.join(to_path, os.path.basename(uri))
+ url = uri
+ url_response = request.urlopen(url)
+ with contextlib.closing(url_response) as fp:
+ with open(to_path, "wb") as out_file:
+ block_size = DEFAULT_BUFFER_SIZE * 8
+ while True:
+ block = fp.read(block_size)
+ if not block:
+ break
+ out_file.write(block)
+ else:
+ if scheme == "oci" and not storage_options:
+ storage_options = default_signer()
+ fs = fsspec.filesystem(scheme, **storage_options)
+ if os.path.isdir(to_path):
+ to_path = os.path.join(
+ to_path, os.path.basename(str(uri).rstrip("/"))
+ )
+ fs.get(uri, to_path, recursive=True)
+ if unpack_path:
+ shutil.unpack_archive(to_path, unpack_path)
+ to_path = unpack_path
From 545aab0e07d5ee9cbfdb56b16e057db59fecd9e7 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Fri, 9 Dec 2022 15:54:43 +0100
Subject: [PATCH 10/52] tarball path provided using CLI argument (source)
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index e793e6707718..33ecb3d5dc29 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -1,8 +1,9 @@
/**
- * @name Arbitrary file write during a remotely downloaded tarball extraction
- * @description Extracting files from a malicious tarball using `shutil.unpack_archive()` without validating
+ * @name Arbitrary file write during a tarball extraction from user controlled source
+ * @description Extracting files from a potentially malicious tarball using `shutil.unpack_archive()` without validating
* that the destination file path is within the destination directory can cause files outside
- * the destination directory to be overwritten. More precisely, if the tarball comes from a remote location.
+ * the destination directory to be overwritten. More precisely, if the tarball comes from a user controlled
+ * location either a remote one or cli argument.
* @kind path-problem
* @id py/unsafe-unpacking
* @problem.severity error
@@ -26,6 +27,14 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) {
// A source coming from a remote location
exists(Http::Client::Request request | source = request)
+ or
+ // A source coming from a CLI argparse module
+ exists(Node o, API::Node ap, MethodCallNode args |
+ ap = API::moduleImport("argparse").getMember("ArgumentParser").getACall().getReturn() and
+ args = ap.getMember("parse_args").getACall() and
+ args.flowsTo(o) and
+ source.(AttrRead).accesses(o, any(string s))
+ )
}
override predicate isSink(DataFlow::Node sink) {
@@ -54,7 +63,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// Accessing the name
exists(AttrRead ar | ar.accesses(nodeFrom, "name") and nodeTo = ar)
or
- // Considering closing use
+ // Considering the use of closing()
exists(API::Node closing |
closing = API::moduleImport("contextlib").getMember("closing") and
closing.getACall().flowsTo(nodeTo) and
From eff132512c3bae3140c87e477d5d62c89d0a7db8 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Sat, 10 Dec 2022 08:15:42 +0100
Subject: [PATCH 11/52] Copying the response data to the archive
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 33ecb3d5dc29..cb87b16432e6 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -53,6 +53,15 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeTo = is.(CallCfgNode).getArg(0)
)
or
+ // Copying the response data to the archive
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and
+ f = mc.getArg(1) and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
+ )
+ or
// Reading the response
exists(MethodCallNode mc |
nodeFrom = mc.getObject() and
@@ -60,8 +69,8 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
mc.flowsTo(nodeTo)
)
or
- // Accessing the name
- exists(AttrRead ar | ar.accesses(nodeFrom, "name") and nodeTo = ar)
+ // Accessing the name or raw content
+ exists(AttrRead ar | ar.accesses(nodeFrom, ["name","raw"]) and nodeTo = ar)
or
// Considering the use of closing()
exists(API::Node closing |
From b19452467df781552790be5fc5bdcce2acacc5f7 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Sat, 10 Dec 2022 21:59:14 +0100
Subject: [PATCH 12/52] read by chunks as additional step
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 32 +++++++++++++------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index cb87b16432e6..159a19b4beec 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -1,8 +1,8 @@
/**
- * @name Arbitrary file write during a tarball extraction from user controlled source
+ * @name Arbitrary file write during a tarball extraction from a user controlled source
* @description Extracting files from a potentially malicious tarball using `shutil.unpack_archive()` without validating
* that the destination file path is within the destination directory can cause files outside
- * the destination directory to be overwritten. More precisely, if the tarball comes from a user controlled
+ * the destination directory to be overwritten. More precisely, if the tarball comes from a user controlled
* location either a remote one or cli argument.
* @kind path-problem
* @id py/unsafe-unpacking
@@ -28,11 +28,11 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// A source coming from a remote location
exists(Http::Client::Request request | source = request)
or
- // A source coming from a CLI argparse module
+ //A source coming from a CLI argparse module
exists(Node o, API::Node ap, MethodCallNode args |
ap = API::moduleImport("argparse").getMember("ArgumentParser").getACall().getReturn() and
args = ap.getMember("parse_args").getACall() and
- args.flowsTo(o) and
+ args.flowsTo(o) and
source.(AttrRead).accesses(o, any(string s))
)
}
@@ -57,7 +57,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
is.flowsTo(f) and
mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and
- f = mc.getArg(1) and
+ f = mc.getArg(1) and
nodeFrom = mc.getArg(0) and
nodeTo = is.(CallCfgNode).getArg(0)
)
@@ -70,13 +70,25 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
)
or
// Accessing the name or raw content
- exists(AttrRead ar | ar.accesses(nodeFrom, ["name","raw"]) and nodeTo = ar)
+ exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
+ or
+ //Use of join of filename
+ exists(API::CallNode mcn |
+ mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and
+ nodeFrom = mcn.getArg(1) and
+ mcn.flowsTo(nodeTo)
+ )
+ or
+ // Read by chunks
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo)
+ )
or
// Considering the use of closing()
- exists(API::Node closing |
- closing = API::moduleImport("contextlib").getMember("closing") and
- closing.getACall().flowsTo(nodeTo) and
- nodeFrom = closing.getACall().getArg(0)
+ exists(API::CallNode closing |
+ closing = API::moduleImport("contextlib").getMember("closing").getACall() and
+ closing.flowsTo(nodeTo) and
+ nodeFrom = closing.getArg(0)
)
)
}
From 2f68b54b27a6e6ba51e27351715823bde362f176 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Mon, 12 Dec 2022 19:46:34 +0100
Subject: [PATCH 13/52] A simple download_file() call from maybe boto3
---
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql | 3 +++
1 file changed, 3 insertions(+)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 159a19b4beec..17604c004263 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -35,6 +35,9 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
args.flowsTo(o) and
source.(AttrRead).accesses(o, any(string s))
)
+ or
+ // A source catching a S3 filename download
+ exists(API::Node s3 | source = s3.getMember("download_file").getACall().getArg(2))
}
override predicate isSink(DataFlow::Node sink) {
From 54109b8ea7ce7b413f2d672b52e2ccfde8795e72 Mon Sep 17 00:00:00 2001
From: ALJI Mohamed
Date: Tue, 13 Dec 2022 15:34:01 +0100
Subject: [PATCH 14/52] Add source wget.download
---
.../ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 17604c004263..f8dfb78fced0 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -38,6 +38,11 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
or
// A source catching a S3 filename download
exists(API::Node s3 | source = s3.getMember("download_file").getACall().getArg(2))
+ or
+ // A source download a file using wget
+ exists(MethodCallNode mcn |
+ mcn = API::moduleImport("wget").getMember("download").getACall() and source = mcn.getArg(1)
+ )
}
override predicate isSink(DataFlow::Node sink) {
From 4376870a518262146c3c2446cd2512be2038b577 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 15 Dec 2022 23:39:02 +0100
Subject: [PATCH 15/52] An uploded file is considered a source
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 122 +++++++++++-------
1 file changed, 75 insertions(+), 47 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index f8dfb78fced0..1ebdf48397c3 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -43,6 +43,21 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
exists(MethodCallNode mcn |
mcn = API::moduleImport("wget").getMember("download").getACall() and source = mcn.getArg(1)
)
+ or
+ // catch the uploaded files as a source
+ exists(Subscript s, Attribute at |
+ at = s.getObject() and at.getAttr() = "FILES" and source.asExpr() = s
+ )
+ or
+ exists(Node obj, AttrRead ar |
+ ar.getAMethodCall("get").flowsTo(source) and
+ ar.accesses(obj, "FILES")
+ )
+ or
+ exists(Node obj, AttrRead ar |
+ ar.getAMethodCall("getlist").flowsTo(source) and
+ ar.accesses(obj, "FILES")
+ )
}
override predicate isSink(DataFlow::Node sink) {
@@ -51,53 +66,66 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
- (
- // Writing the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc.getMethodName() = "write" and
- f = mc.getObject() and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
- or
- // Copying the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and
- f = mc.getArg(1) and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
- or
- // Reading the response
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and
- mc.getMethodName() = "read" and
- mc.flowsTo(nodeTo)
- )
- or
- // Accessing the name or raw content
- exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
- or
- //Use of join of filename
- exists(API::CallNode mcn |
- mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and
- nodeFrom = mcn.getArg(1) and
- mcn.flowsTo(nodeTo)
- )
- or
- // Read by chunks
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo)
- )
- or
- // Considering the use of closing()
- exists(API::CallNode closing |
- closing = API::moduleImport("contextlib").getMember("closing").getACall() and
- closing.flowsTo(nodeTo) and
- nodeFrom = closing.getArg(0)
- )
+ // Writing the response data to the archive
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc.getMethodName() = "write" and
+ f = mc.getObject() and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
+ )
+ or
+ // Copying the response data to the archive
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and
+ f = mc.getArg(1) and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
+ )
+ or
+ // Reading the response
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and
+ mc.getMethodName() = "read" and
+ mc.flowsTo(nodeTo)
+ )
+ or
+ // Accessing the name or raw content
+ exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
+ or
+ //Use of join of filename
+ exists(API::CallNode mcn |
+ mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and
+ nodeFrom = mcn.getArg(1) and
+ mcn.flowsTo(nodeTo)
+ )
+ or
+ // Read by chunks
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo)
+ )
+ or
+ // Considering the use of closing()
+ exists(API::CallNode closing |
+ closing = API::moduleImport("contextlib").getMember("closing").getACall() and
+ closing.flowsTo(nodeTo) and
+ nodeFrom = closing.getArg(0)
+ )
+ or
+ // Considering the use of "fs"
+ exists(API::CallNode fs, MethodCallNode mcn |
+ fs =
+ API::moduleImport("django")
+ .getMember("core")
+ .getMember("files")
+ .getMember("storage")
+ .getMember("FileSystemStorage")
+ .getACall() and
+ fs.flowsTo(mcn.getObject()) and
+ mcn.getMethodName() = ["save", "path"] and
+ nodeFrom = mcn.getArg(0) and
+ nodeTo = mcn
)
}
}
From 10d6ebf95b5231c09440f401521376049c2325a6 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Wed, 25 Jan 2023 19:28:05 +0100
Subject: [PATCH 16/52] Use of inline tests for dataflow queries
---
.../query-tests/Security/CWE-022/DataflowQueryTest.expected | 4 ++++
.../query-tests/Security/CWE-022/DataflowQueryTest.ql | 2 ++
.../experimental/query-tests/Security/CWE-022/UnsafeUnpack.py | 4 ++--
3 files changed, 8 insertions(+), 2 deletions(-)
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
new file mode 100644
index 000000000000..34c941949244
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
@@ -0,0 +1,4 @@
+missingAnnotationOnSink
+failures
+| UnsafeUnpack.py:12:46:12:58 | Comment # $result=BAD | Missing result:result=BAD |
+| UnsafeUnpack.py:55:53:55:65 | Comment # $result=BAD | Missing result:result=BAD |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
new file mode 100644
index 000000000000..29ccba982aac
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
@@ -0,0 +1,2 @@
+import python
+import experimental.dataflow.TestUtil.DataflowQueryTest
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index c7820e52b04b..b6015bed9083 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -9,7 +9,7 @@
f.write(response.raw.read())
untarredpath = "/tmp/tmp123"
-shutil.unpack_archive(tarpath, untarredpath)
+shutil.unpack_archive(tarpath, untarredpath) # $result=BAD
import tempfile
@@ -52,5 +52,5 @@
)
fs.get(uri, to_path, recursive=True)
if unpack_path:
- shutil.unpack_archive(to_path, unpack_path)
+ shutil.unpack_archive(to_path, unpack_path) # $result=BAD
to_path = unpack_path
From 0ed480855a8b38dd573e29f6b8112fa4890fd4c9 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Wed, 25 Jan 2023 19:44:28 +0100
Subject: [PATCH 17/52] Update
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
Yes, definitely
Co-authored-by: yoff
---
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 1ebdf48397c3..2fdbc29dc43f 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -69,8 +69,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// Writing the response data to the archive
exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
is.flowsTo(f) and
- mc.getMethodName() = "write" and
- f = mc.getObject() and
+ mc.calls(f, "write")
nodeFrom = mc.getArg(0) and
nodeTo = is.(CallCfgNode).getArg(0)
)
From 2d38993075a6a5a319b6e493b536f0647e15bf9b Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Wed, 25 Jan 2023 19:46:13 +0100
Subject: [PATCH 18/52] Add a missing "and"
---
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 2fdbc29dc43f..6cb52a489948 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -69,7 +69,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// Writing the response data to the archive
exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
is.flowsTo(f) and
- mc.calls(f, "write")
+ mc.calls(f, "write") and
nodeFrom = mc.getArg(0) and
nodeTo = is.(CallCfgNode).getArg(0)
)
From 22af6f518272f6e9883bbbc5af3cc1b7dd25c3d1 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Wed, 25 Jan 2023 23:00:00 +0100
Subject: [PATCH 19/52] Restrict download_file() to boto3 lib
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 10 ++++++++--
.../query-tests/Security/CWE-022/UnsafeUnpack.py | 14 ++++++++++++++
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 6cb52a489948..965392856c56 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -36,8 +36,14 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
source.(AttrRead).accesses(o, any(string s))
)
or
- // A source catching a S3 filename download
- exists(API::Node s3 | source = s3.getMember("download_file").getACall().getArg(2))
+ // A source catching an S3 filename download
+ // see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
+ exists(MethodCallNode mcn, Node s3, Node bc |
+ bc = API::moduleImport("boto3").getMember("client").getACall() and
+ bc = s3.getALocalSource() and
+ mcn.calls(s3, "download_file") and
+ source = mcn.getArg(2)
+ )
or
// A source download a file using wget
exists(MethodCallNode mcn |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index b6015bed9083..13c3a32cd5a7 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -54,3 +54,17 @@
if unpack_path:
shutil.unpack_archive(to_path, unpack_path) # $result=BAD
to_path = unpack_path
+
+
+# A source catching an S3 filename download
+# see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
+import boto3
+
+remote_ziped_name = "remote_name.tar.gz"
+base_dir = "/tmp/basedir"
+local_ziped_path = os.path.join(base_dir, remote_ziped_name)
+bucket_name = "mybucket"
+
+s3 = boto3.client('s3')
+s3.download_file(bucket_name, remote_ziped_name, local_ziped_path)
+shutil.unpack_archive(local_ziped_path, base_dir) # $result=BAD
\ No newline at end of file
From 9b5b0c60b83fcf112dc0e5778d2ead8c77c86bbc Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 00:02:20 +0100
Subject: [PATCH 20/52] Handle the download of a tarball using wget pkg.
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 10 +++++++--
.../Security/CWE-022/UnsafeUnpack.py | 21 ++++++++++++++++++-
2 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 965392856c56..d9c5ffd6f766 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -46,8 +46,14 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
)
or
// A source download a file using wget
- exists(MethodCallNode mcn |
- mcn = API::moduleImport("wget").getMember("download").getACall() and source = mcn.getArg(1)
+ // see wget: https://pypi.org/project/wget/
+ exists(API::CallNode mcn |
+ mcn = API::moduleImport("wget").getMember("download").getACall() and
+ (
+ source = mcn.getArg(1)
+ or
+ source = mcn.getReturn().asSource() and not exists(Node arg | arg = mcn.getArg(1))
+ )
)
or
// catch the uploaded files as a source
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index 13c3a32cd5a7..8386f2770b50 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -59,6 +59,7 @@
# A source catching an S3 filename download
# see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
import boto3
+import os
remote_ziped_name = "remote_name.tar.gz"
base_dir = "/tmp/basedir"
@@ -67,4 +68,22 @@
s3 = boto3.client('s3')
s3.download_file(bucket_name, remote_ziped_name, local_ziped_path)
-shutil.unpack_archive(local_ziped_path, base_dir) # $result=BAD
\ No newline at end of file
+shutil.unpack_archive(local_ziped_path, base_dir) # $result=BAD
+
+
+# wget
+# see wget: https://pypi.org/project/wget/
+import wget
+import os
+
+url = "https://some.remote/location/remote_name.tar.xz"
+compressed_file = "/tmp/basedir/local_name.tar.xz"
+base_dir = "/tmp/basedir"
+
+# download(url, out, bar) contains out parameter
+wget.download(url, compressed_file)
+shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
+
+# download(url) returns filename
+compressed_file = wget.download(url)
+shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
From f867c9008fdd5c4d03331b581bf8594149291d30 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 00:08:54 +0100
Subject: [PATCH 21/52] Commit the expected results
---
.../Security/CWE-022/DataflowQueryTest.expected | 3 +++
.../Security/CWE-022/UnsafeUnpack.expected | 12 ++++++++++++
2 files changed, 15 insertions(+)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
index 34c941949244..99e42e4cccb8 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
@@ -2,3 +2,6 @@ missingAnnotationOnSink
failures
| UnsafeUnpack.py:12:46:12:58 | Comment # $result=BAD | Missing result:result=BAD |
| UnsafeUnpack.py:55:53:55:65 | Comment # $result=BAD | Missing result:result=BAD |
+| UnsafeUnpack.py:71:51:71:63 | Comment # $result=BAD | Missing result:result=BAD |
+| UnsafeUnpack.py:85:50:85:62 | Comment # $result=BAD | Missing result:result=BAD |
+| UnsafeUnpack.py:89:50:89:62 | Comment # $result=BAD | Missing result:result=BAD |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
index b1e93bf3ab28..78214a634b9f 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -2,13 +2,25 @@ edges
| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute |
| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath |
| UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path |
+| UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file |
nodes
| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
| UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | semmle.label | ControlFlowNode for to_path |
+| UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
subpaths
#select
| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
| UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
From 2e4cb63049307e68d6228969c7fefe682effedaa Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 01:00:01 +0100
Subject: [PATCH 22/52] Optimize the Argparse filename as a source.
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 17 +++++++++++------
.../Security/CWE-022/UnsafeUnpack.py | 14 ++++++++++++++
2 files changed, 25 insertions(+), 6 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index d9c5ffd6f766..052ca3e5d983 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -28,12 +28,17 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// A source coming from a remote location
exists(Http::Client::Request request | source = request)
or
- //A source coming from a CLI argparse module
- exists(Node o, API::Node ap, MethodCallNode args |
- ap = API::moduleImport("argparse").getMember("ArgumentParser").getACall().getReturn() and
- args = ap.getMember("parse_args").getACall() and
- args.flowsTo(o) and
- source.(AttrRead).accesses(o, any(string s))
+ // A source coming from a CLI argparse module
+ // see argparse: https://docs.python.org/3/library/argparse.html
+ exists(MethodCallNode args |
+ args = source.(AttrRead).getObject().getALocalSource() and
+ args =
+ API::moduleImport("argparse")
+ .getMember("ArgumentParser")
+ .getACall()
+ .getReturn()
+ .getMember("parse_args")
+ .getACall()
)
or
// A source catching an S3 filename download
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index 8386f2770b50..eb0ac597a1a0 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -87,3 +87,17 @@
# download(url) returns filename
compressed_file = wget.download(url)
shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
+
+
+# A source coming from a CLI argparse module
+# see argparse: https://docs.python.org/3/library/argparse.html
+import argparse
+
+parser = argparse.ArgumentParser(description='Process some integers.')
+parser.add_argument('integers', metavar='N', type=int, nargs='+',
+ help='an integer for the accumulator')
+parser.add_argument('filename', help='filename to be provided')
+
+args = parser.parse_args()
+compressed_file = args.filename
+shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
\ No newline at end of file
From 9464940214d59899cc16b61cdb4eaed629010fbf Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 01:00:19 +0100
Subject: [PATCH 23/52] Add expected results for argparse source
---
.../query-tests/Security/CWE-022/DataflowQueryTest.expected | 1 +
.../query-tests/Security/CWE-022/UnsafeUnpack.expected | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
index 99e42e4cccb8..ae1beb23a86c 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
@@ -5,3 +5,4 @@ failures
| UnsafeUnpack.py:71:51:71:63 | Comment # $result=BAD | Missing result:result=BAD |
| UnsafeUnpack.py:85:50:85:62 | Comment # $result=BAD | Missing result:result=BAD |
| UnsafeUnpack.py:89:50:89:62 | Comment # $result=BAD | Missing result:result=BAD |
+| UnsafeUnpack.py:103:50:103:62 | Comment # $result=BAD | Missing result:result=BAD |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
index 78214a634b9f..a2eff1ddd1da 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -5,6 +5,7 @@ edges
| UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path |
| UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file |
| UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:102:19:102:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file |
nodes
| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
@@ -17,6 +18,8 @@ nodes
| UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
| UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:102:19:102:31 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
subpaths
#select
| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
@@ -24,3 +27,4 @@ subpaths
| UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
| UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
| UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:102:19:102:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
From aaa004061252b19ef77c6084652dbc2bd4ca487c Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 08:53:47 +0100
Subject: [PATCH 24/52] Seperate the dataflow config from the query
---
.../Security/CWE-022bis/UnsafeUnpack.ql | 132 +----------------
.../Security/CWE-022bis/UnsafeUnpackQuery.qll | 137 ++++++++++++++++++
.../CWE-022/DataflowQueryTest.expected | 6 -
.../Security/CWE-022/DataflowQueryTest.ql | 1 +
4 files changed, 140 insertions(+), 136 deletions(-)
create mode 100644 python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 052ca3e5d983..1e17cb728cab 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -10,141 +10,13 @@
* @security-severity 7.5
* @precision high
* @tags security
+ * experimental
* external/cwe/cwe-022
*/
import python
-import semmle.python.Concepts
-import semmle.python.dataflow.new.internal.DataFlowPublic
-import semmle.python.ApiGraphs
+import UnsafeUnpackQuery
import DataFlow::PathGraph
-import semmle.python.dataflow.new.TaintTracking
-import semmle.python.frameworks.Stdlib
-
-class UnsafeUnpackingConfig extends TaintTracking::Configuration {
- UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" }
-
- override predicate isSource(DataFlow::Node source) {
- // A source coming from a remote location
- exists(Http::Client::Request request | source = request)
- or
- // A source coming from a CLI argparse module
- // see argparse: https://docs.python.org/3/library/argparse.html
- exists(MethodCallNode args |
- args = source.(AttrRead).getObject().getALocalSource() and
- args =
- API::moduleImport("argparse")
- .getMember("ArgumentParser")
- .getACall()
- .getReturn()
- .getMember("parse_args")
- .getACall()
- )
- or
- // A source catching an S3 filename download
- // see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
- exists(MethodCallNode mcn, Node s3, Node bc |
- bc = API::moduleImport("boto3").getMember("client").getACall() and
- bc = s3.getALocalSource() and
- mcn.calls(s3, "download_file") and
- source = mcn.getArg(2)
- )
- or
- // A source download a file using wget
- // see wget: https://pypi.org/project/wget/
- exists(API::CallNode mcn |
- mcn = API::moduleImport("wget").getMember("download").getACall() and
- (
- source = mcn.getArg(1)
- or
- source = mcn.getReturn().asSource() and not exists(Node arg | arg = mcn.getArg(1))
- )
- )
- or
- // catch the uploaded files as a source
- exists(Subscript s, Attribute at |
- at = s.getObject() and at.getAttr() = "FILES" and source.asExpr() = s
- )
- or
- exists(Node obj, AttrRead ar |
- ar.getAMethodCall("get").flowsTo(source) and
- ar.accesses(obj, "FILES")
- )
- or
- exists(Node obj, AttrRead ar |
- ar.getAMethodCall("getlist").flowsTo(source) and
- ar.accesses(obj, "FILES")
- )
- }
-
- override predicate isSink(DataFlow::Node sink) {
- // A sink capturing method calls to `unpack_archive`.
- sink = API::moduleImport("shutil").getMember("unpack_archive").getACall().getArg(0)
- }
-
- override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
- // Writing the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc.calls(f, "write") and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
- or
- // Copying the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and
- f = mc.getArg(1) and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
- or
- // Reading the response
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and
- mc.getMethodName() = "read" and
- mc.flowsTo(nodeTo)
- )
- or
- // Accessing the name or raw content
- exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
- or
- //Use of join of filename
- exists(API::CallNode mcn |
- mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and
- nodeFrom = mcn.getArg(1) and
- mcn.flowsTo(nodeTo)
- )
- or
- // Read by chunks
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo)
- )
- or
- // Considering the use of closing()
- exists(API::CallNode closing |
- closing = API::moduleImport("contextlib").getMember("closing").getACall() and
- closing.flowsTo(nodeTo) and
- nodeFrom = closing.getArg(0)
- )
- or
- // Considering the use of "fs"
- exists(API::CallNode fs, MethodCallNode mcn |
- fs =
- API::moduleImport("django")
- .getMember("core")
- .getMember("files")
- .getMember("storage")
- .getMember("FileSystemStorage")
- .getACall() and
- fs.flowsTo(mcn.getObject()) and
- mcn.getMethodName() = ["save", "path"] and
- nodeFrom = mcn.getArg(0) and
- nodeTo = mcn
- )
- }
-}
from UnsafeUnpackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
new file mode 100644
index 000000000000..5794d9e32f2e
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
@@ -0,0 +1,137 @@
+/**
+ *
+ * Provides a taint-tracking configuration for detecting "UnsafeUnpacking" vulnerabilities.
+ *
+ */
+
+import python
+import semmle.python.Concepts
+import semmle.python.dataflow.new.internal.DataFlowPublic
+import semmle.python.ApiGraphs
+import semmle.python.dataflow.new.TaintTracking
+import semmle.python.frameworks.Stdlib
+
+class UnsafeUnpackingConfig extends TaintTracking::Configuration {
+ UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" }
+
+ override predicate isSource(DataFlow::Node source) {
+ // A source coming from a remote location
+ exists(Http::Client::Request request | source = request)
+ or
+ // A source coming from a CLI argparse module
+ // see argparse: https://docs.python.org/3/library/argparse.html
+ exists(MethodCallNode args |
+ args = source.(AttrRead).getObject().getALocalSource() and
+ args =
+ API::moduleImport("argparse")
+ .getMember("ArgumentParser")
+ .getACall()
+ .getReturn()
+ .getMember("parse_args")
+ .getACall()
+ )
+ or
+ // A source catching an S3 filename download
+ // see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
+ exists(MethodCallNode mcn, Node s3, Node bc |
+ bc = API::moduleImport("boto3").getMember("client").getACall() and
+ bc = s3.getALocalSource() and
+ mcn.calls(s3, "download_file") and
+ source = mcn.getArg(2)
+ )
+ or
+ // A source download a file using wget
+ // see wget: https://pypi.org/project/wget/
+ exists(API::CallNode mcn |
+ mcn = API::moduleImport("wget").getMember("download").getACall() and
+ (
+ source = mcn.getArg(1)
+ or
+ source = mcn.getReturn().asSource() and not exists(Node arg | arg = mcn.getArg(1))
+ )
+ )
+ or
+ // catch the uploaded files as a source
+ exists(Subscript s, Attribute at |
+ at = s.getObject() and at.getAttr() = "FILES" and source.asExpr() = s
+ )
+ or
+ exists(Node obj, AttrRead ar |
+ ar.getAMethodCall("get").flowsTo(source) and
+ ar.accesses(obj, "FILES")
+ )
+ or
+ exists(Node obj, AttrRead ar |
+ ar.getAMethodCall("getlist").flowsTo(source) and
+ ar.accesses(obj, "FILES")
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ // A sink capturing method calls to `unpack_archive`.
+ sink = API::moduleImport("shutil").getMember("unpack_archive").getACall().getArg(0)
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ // Writing the response data to the archive
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc.calls(f, "write") and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
+ )
+ or
+ // Copying the response data to the archive
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and
+ f = mc.getArg(1) and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
+ )
+ or
+ // Reading the response
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and
+ mc.getMethodName() = "read" and
+ mc.flowsTo(nodeTo)
+ )
+ or
+ // Accessing the name or raw content
+ exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
+ or
+ //Use of join of filename
+ exists(API::CallNode mcn |
+ mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and
+ nodeFrom = mcn.getArg(1) and
+ mcn.flowsTo(nodeTo)
+ )
+ or
+ // Read by chunks
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo)
+ )
+ or
+ // Considering the use of closing()
+ exists(API::CallNode closing |
+ closing = API::moduleImport("contextlib").getMember("closing").getACall() and
+ closing.flowsTo(nodeTo) and
+ nodeFrom = closing.getArg(0)
+ )
+ or
+ // Considering the use of "fs"
+ exists(API::CallNode fs, MethodCallNode mcn |
+ fs =
+ API::moduleImport("django")
+ .getMember("core")
+ .getMember("files")
+ .getMember("storage")
+ .getMember("FileSystemStorage")
+ .getACall() and
+ fs.flowsTo(mcn.getObject()) and
+ mcn.getMethodName() = ["save", "path"] and
+ nodeFrom = mcn.getArg(0) and
+ nodeTo = mcn
+ )
+ }
+}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
index ae1beb23a86c..3875da4e143c 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
@@ -1,8 +1,2 @@
missingAnnotationOnSink
failures
-| UnsafeUnpack.py:12:46:12:58 | Comment # $result=BAD | Missing result:result=BAD |
-| UnsafeUnpack.py:55:53:55:65 | Comment # $result=BAD | Missing result:result=BAD |
-| UnsafeUnpack.py:71:51:71:63 | Comment # $result=BAD | Missing result:result=BAD |
-| UnsafeUnpack.py:85:50:85:62 | Comment # $result=BAD | Missing result:result=BAD |
-| UnsafeUnpack.py:89:50:89:62 | Comment # $result=BAD | Missing result:result=BAD |
-| UnsafeUnpack.py:103:50:103:62 | Comment # $result=BAD | Missing result:result=BAD |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
index 29ccba982aac..e3cd5f784ff3 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
@@ -1,2 +1,3 @@
import python
import experimental.dataflow.TestUtil.DataflowQueryTest
+import UnsafeUnpackQuery
\ No newline at end of file
From 54cc4d6498d90a5b711a9eead2dca298ae860b64 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 12:51:55 +0100
Subject: [PATCH 25/52] Opt for any source from RemoteFlowSource.
---
.../Security/CWE-022bis/UnsafeUnpackQuery.qll | 5 +-
.../Security/CWE-022/UnsafeUnpack.expected | 62 +++++++-----
.../Security/CWE-022/UnsafeUnpack.py | 97 ++++++++-----------
3 files changed, 79 insertions(+), 85 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
index 5794d9e32f2e..c041b710ae0d 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
@@ -1,7 +1,5 @@
/**
- *
* Provides a taint-tracking configuration for detecting "UnsafeUnpacking" vulnerabilities.
- *
*/
import python
@@ -10,13 +8,14 @@ import semmle.python.dataflow.new.internal.DataFlowPublic
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
import semmle.python.frameworks.Stdlib
+import semmle.python.dataflow.new.RemoteFlowSources
class UnsafeUnpackingConfig extends TaintTracking::Configuration {
UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" }
override predicate isSource(DataFlow::Node source) {
// A source coming from a remote location
- exists(Http::Client::Request request | source = request)
+ source instanceof RemoteFlowSource
or
// A source coming from a CLI argparse module
// see argparse: https://docs.python.org/3/library/argparse.html
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
index a2eff1ddd1da..cda42c13267c 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -1,30 +1,40 @@
edges
-| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath |
-| UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path |
-| UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path |
-| UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:102:19:102:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request | UnsafeUnpack.py:11:16:11:22 | ControlFlowNode for request |
+| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:5:26:5:32 | GSSA Variable request |
+| UnsafeUnpack.py:5:26:5:32 | GSSA Variable request | UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request |
+| UnsafeUnpack.py:11:16:11:22 | ControlFlowNode for request | UnsafeUnpack.py:11:16:11:27 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:11:16:11:27 | ControlFlowNode for Attribute | UnsafeUnpack.py:17:23:17:34 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:17:23:17:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:34:50:34:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:48:20:48:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:52:19:52:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:66:19:66:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:80:16:80:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:86:15:86:26 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:86:15:86:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath |
nodes
-| UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| UnsafeUnpack.py:9:15:9:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
-| UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | semmle.label | ControlFlowNode for to_path |
-| UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
-| UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
-| UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:102:19:102:31 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request | semmle.label | ModuleVariableNode for UnsafeUnpack.request |
+| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
+| UnsafeUnpack.py:5:26:5:32 | GSSA Variable request | semmle.label | GSSA Variable request |
+| UnsafeUnpack.py:11:16:11:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| UnsafeUnpack.py:11:16:11:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:17:23:17:34 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:34:50:34:65 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:48:20:48:34 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:52:19:52:36 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:66:19:66:31 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:80:16:80:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:86:15:86:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
subpaths
#select
-| UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:12:5:41 | ControlFlowNode for Attribute() | UnsafeUnpack.py:12:23:12:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | UnsafeUnpack.py:36:24:36:43 | ControlFlowNode for Attribute() | UnsafeUnpack.py:55:31:55:37 | ControlFlowNode for to_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:70:50:70:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:71:23:71:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:84:20:84:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:85:23:85:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:88:19:88:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:89:23:89:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:102:19:102:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:103:23:103:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:34:50:34:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:48:20:48:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:52:19:52:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:66:19:66:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:80:16:80:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index eb0ac597a1a0..b2d23f2dbe01 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -1,60 +1,24 @@
import requests
import shutil
-
-url = "https://www.someremote.location/tarball.tar.gz"
-response = requests.get(url, stream=True)
-
-tarpath = "/tmp/tmp456/tarball.tar.gz"
-with open(tarpath, "wb") as f:
- f.write(response.raw.read())
-
-untarredpath = "/tmp/tmp123"
-shutil.unpack_archive(tarpath, untarredpath) # $result=BAD
-
-
-import tempfile
-import os
-from urllib import request
-import contextlib
-import shutil
-
-unpack = True
-to_path = "/tmp/tmp123"
-uri = "https://www.goog.com/zzz.tar.gz"
-scheme = "https"
-
-with tempfile.TemporaryDirectory() as temp_dir:
- if unpack and (str(uri).endswith("zip") or str(uri).endswith("tar.gz")):
- unpack_path = to_path
- to_path = temp_dir
- else:
- unpack_path = None
- if scheme in ["http", "https", "ftp"]:
- if os.path.isdir(to_path):
- to_path = os.path.join(to_path, os.path.basename(uri))
- url = uri
- url_response = request.urlopen(url)
- with contextlib.closing(url_response) as fp:
- with open(to_path, "wb") as out_file:
- block_size = DEFAULT_BUFFER_SIZE * 8
- while True:
- block = fp.read(block_size)
- if not block:
- break
- out_file.write(block)
- else:
- if scheme == "oci" and not storage_options:
- storage_options = default_signer()
- fs = fsspec.filesystem(scheme, **storage_options)
- if os.path.isdir(to_path):
- to_path = os.path.join(
- to_path, os.path.basename(str(uri).rstrip("/"))
- )
- fs.get(uri, to_path, recursive=True)
- if unpack_path:
- shutil.unpack_archive(to_path, unpack_path) # $result=BAD
- to_path = unpack_path
-
+import os
+
+from flask import Flask, request
+app = Flask(__name__)
+
+# Consider any RemoteFlowSource as a source
+@app.route("/download_from_url")
+def download_from_url():
+ filename = request.args.get('filename', '')
+ if not filename:
+ response = requests.get(filename, stream=True)
+
+ tarpath = "/tmp/tmp456/tarball.tar.gz"
+ with open(tarpath, "wb") as f:
+ f.write(response.raw.read())
+
+ untarredpath = "/tmp/tmp123"
+ shutil.unpack_archive(tarpath, untarredpath) # $result=BAD
+
# A source catching an S3 filename download
# see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
@@ -100,4 +64,25 @@
args = parser.parse_args()
compressed_file = args.filename
-shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
\ No newline at end of file
+shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
+
+
+# A source coming from a CLI and downloaded
+import argparse
+import requests
+
+parser = argparse.ArgumentParser(description='Process some integers.')
+parser.add_argument('integers', metavar='N', type=int, nargs='+',
+ help='an integer for the accumulator')
+parser.add_argument('filename', help='url to filename to be provided')
+
+args = parser.parse_args()
+url_filename = args.filename
+
+response = requests.get(url_filename, stream=True)
+
+tarpath = "/tmp/tmp456/tarball.tar.gz"
+with open(tarpath, "wb") as f:
+ f.write(response.raw.read())
+
+shutil.unpack_archive(tarpath, base_dir) # $result=BAD
\ No newline at end of file
From 51b11de44a24926449543f837d9d1ef8c91408b9 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 15:16:24 +0100
Subject: [PATCH 26/52] Add a Django Upload examples
---
.../Security/CWE-022bis/UnsafeUnpackQuery.qll | 25 ++++++++----
.../Security/CWE-022/UnsafeUnpack.py | 40 ++++++++++++++++++-
2 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
index c041b710ae0d..29c41b74dab7 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
@@ -55,13 +55,10 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
at = s.getObject() and at.getAttr() = "FILES" and source.asExpr() = s
)
or
+ // Retrieve Django uploaded files
+ // see HttpRequest.FILES: https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.HttpRequest.FILES
exists(Node obj, AttrRead ar |
- ar.getAMethodCall("get").flowsTo(source) and
- ar.accesses(obj, "FILES")
- )
- or
- exists(Node obj, AttrRead ar |
- ar.getAMethodCall("getlist").flowsTo(source) and
+ ar.getAMethodCall(["getlist", "get"]).flowsTo(source) and
ar.accesses(obj, "FILES")
)
}
@@ -93,7 +90,21 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
exists(MethodCallNode mc |
nodeFrom = mc.getObject() and
mc.getMethodName() = "read" and
- mc.flowsTo(nodeTo)
+ nodeTo = mc
+ )
+ or
+ // Open for access
+ exists(MethodCallNode cn |
+ nodeTo = cn.getObject() and
+ cn.getMethodName() = "open" and
+ cn.flowsTo(nodeFrom)
+ )
+ or
+ // Write for access
+ exists(MethodCallNode cn |
+ nodeTo = cn.getObject() and
+ cn.getMethodName() = "write" and
+ nodeFrom = cn.getArg(0)
)
or
// Accessing the name or raw content
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index b2d23f2dbe01..fe1b6fa6d456 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -85,4 +85,42 @@ def download_from_url():
with open(tarpath, "wb") as f:
f.write(response.raw.read())
-shutil.unpack_archive(tarpath, base_dir) # $result=BAD
\ No newline at end of file
+shutil.unpack_archive(tarpath, base_dir) # $result=BAD
+
+# the django upload functionality
+# see HttpRequest.FILES: https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.HttpRequest.FILES
+from django.shortcuts import render
+from django.core.files.storage import FileSystemStorage
+import shutil
+
+def simple_upload(request):
+
+ base_dir = "/tmp/baase_dir"
+ if request.method == 'POST':
+ # Read uploaded files by chunks of data
+ # see chunks(): https://docs.djangoproject.com/en/4.1/ref/files/uploads/#django.core.files.uploadedfile.UploadedFile.chunks
+ savepath = os.path.join(base_dir, "tarball_compressed.tar.gz")
+ with open(savepath, 'wb+') as wfile:
+ for chunk in request.FILES["ufile1"].chunks():
+ wfile.write(chunk)
+ shutil.unpack_archive(savepath, base_dir) # $result=BAD
+
+ # Write in binary the uploaded tarball
+ myfile = request.FILES.get("ufile1")
+ file_path = os.path.join(base_dir, "tarball.tar")
+ with file_path.open('wb') as f:
+ f.write(myfile.read())
+ shutil.unpack_archive(file_path, base_dir) # $result=BAD
+
+ # Save uploaded files using FileSystemStorage Django API
+ # see FileSystemStorage: https://docs.djangoproject.com/en/4.1/ref/files/storage/#django.core.files.storage.FileSystemStorage
+ for ufile in request.FILES.getlist():
+ fs = FileSystemStorage()
+ filename = fs.save(ufile.name, ufile)
+ uploaded_file_path = fs.path(filename)
+ shutil.unpack_archive(uploaded_file_path, base_dir) # $result=BAD
+
+ return render(request, 'simple_upload.html')
+
+ elif request.method == 'GET':
+ return render(request, 'simple_upload.html')
\ No newline at end of file
From 1a211485a47c814f461ccfb8488b3a1626e2a845 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 17:07:59 +0100
Subject: [PATCH 27/52] Restrain the source and add two steps.
---
.../Security/CWE-022bis/UnsafeUnpackQuery.qll | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
index 29c41b74dab7..7409d30fdfa5 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
@@ -50,17 +50,9 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
)
)
or
- // catch the uploaded files as a source
- exists(Subscript s, Attribute at |
- at = s.getObject() and at.getAttr() = "FILES" and source.asExpr() = s
- )
- or
- // Retrieve Django uploaded files
+ // catch the Django uploaded files as a source
// see HttpRequest.FILES: https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.HttpRequest.FILES
- exists(Node obj, AttrRead ar |
- ar.getAMethodCall(["getlist", "get"]).flowsTo(source) and
- ar.accesses(obj, "FILES")
- )
+ source.(AttrRead).getAttributeName() = "FILES"
}
override predicate isSink(DataFlow::Node sink) {
@@ -107,6 +99,12 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeFrom = cn.getArg(0)
)
or
+ // Retrieve Django uploaded files
+ // see HttpRequest.FILES.getlist(): https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.QueryDict.getlist
+ exists(MethodCallNode mc |
+ nodeFrom = mc.getObject() and mc.getMethodName() = ["getlist", "get"] and nodeTo = mc
+ )
+ or
// Accessing the name or raw content
exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
or
From 998f1bf215ea357943baf702bc98d5efce68ddb8 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 26 Jan 2023 18:54:36 +0100
Subject: [PATCH 28/52] Some reformatting
---
.../Security/CWE-022bis/UnsafeUnpackQuery.qll | 69 +++++++++----------
.../Security/CWE-022/UnsafeUnpack.py | 19 +++--
2 files changed, 40 insertions(+), 48 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
index 7409d30fdfa5..4e7e97188c0a 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
@@ -61,23 +61,6 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
- // Writing the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc.calls(f, "write") and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
- or
- // Copying the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc = API::moduleImport("shutil").getMember("copyfileobj").getACall() and
- f = mc.getArg(1) and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
- or
// Reading the response
exists(MethodCallNode mc |
nodeFrom = mc.getObject() and
@@ -94,20 +77,37 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
or
// Write for access
exists(MethodCallNode cn |
- nodeTo = cn.getObject() and
+ nodeFrom = cn.getObject() and
cn.getMethodName() = "write" and
- nodeFrom = cn.getArg(0)
+ nodeTo = cn.getArg(0)
)
or
// Retrieve Django uploaded files
// see HttpRequest.FILES.getlist(): https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.QueryDict.getlist
exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and mc.getMethodName() = ["getlist", "get"] and nodeTo = mc
+ nodeFrom = mc.getObject() and
+ mc.getMethodName() = ["getlist", "get"] and
+ nodeTo = mc
)
or
// Accessing the name or raw content
exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
or
+ // Considering the use of "fs"
+ exists(API::CallNode fs, MethodCallNode mcn |
+ fs =
+ API::moduleImport("django")
+ .getMember("core")
+ .getMember("files")
+ .getMember("storage")
+ .getMember("FileSystemStorage")
+ .getACall() and
+ fs.flowsTo(mcn.getObject()) and
+ mcn.getMethodName() = ["save", "path"] and
+ nodeFrom = mcn.getArg(0) and
+ nodeTo = mcn
+ )
+ or
//Use of join of filename
exists(API::CallNode mcn |
mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and
@@ -120,26 +120,19 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo)
)
or
- // Considering the use of closing()
- exists(API::CallNode closing |
- closing = API::moduleImport("contextlib").getMember("closing").getACall() and
- closing.flowsTo(nodeTo) and
- nodeFrom = closing.getArg(0)
+ // Write access
+ exists(MethodCallNode cn |
+ nodeTo = cn.getObject() and
+ cn.getMethodName() = "write" and
+ nodeFrom = cn.getArg(0)
)
or
- // Considering the use of "fs"
- exists(API::CallNode fs, MethodCallNode mcn |
- fs =
- API::moduleImport("django")
- .getMember("core")
- .getMember("files")
- .getMember("storage")
- .getMember("FileSystemStorage")
- .getACall() and
- fs.flowsTo(mcn.getObject()) and
- mcn.getMethodName() = ["save", "path"] and
- nodeFrom = mcn.getArg(0) and
- nodeTo = mcn
+ // Writing the response data to the archive
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc.calls(f, "write") and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
)
}
}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index fe1b6fa6d456..ae9ee0559992 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -8,16 +8,15 @@
# Consider any RemoteFlowSource as a source
@app.route("/download_from_url")
def download_from_url():
- filename = request.args.get('filename', '')
- if not filename:
- response = requests.get(filename, stream=True)
-
- tarpath = "/tmp/tmp456/tarball.tar.gz"
- with open(tarpath, "wb") as f:
- f.write(response.raw.read())
-
- untarredpath = "/tmp/tmp123"
- shutil.unpack_archive(tarpath, untarredpath) # $result=BAD
+ filename = request.args.get('filename', '')
+ if not filename:
+ response = requests.get(filename, stream=True)
+
+ tarpath = "/tmp/tmp456/tarball.tar.gz"
+ with open(tarpath, "wb") as f:
+ f.write(response.raw.read())
+ untarredpath = "/tmp/tmp123"
+ shutil.unpack_archive(tarpath, untarredpath) # $result=BAD
# A source catching an S3 filename download
From bca053f85554cd0d21a55219740d275035c70f16 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 13:42:14 +0100
Subject: [PATCH 29/52] Move the config query to the parent directory
---
.../experimental/Security/{CWE-022bis => }/UnsafeUnpackQuery.qll | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename python/ql/src/experimental/Security/{CWE-022bis => }/UnsafeUnpackQuery.qll (100%)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
similarity index 100%
rename from python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpackQuery.qll
rename to python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
From 5f0bf1053a3515faf16ec0ef033ab6b2511c97e6 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 13:42:57 +0100
Subject: [PATCH 30/52] Update the dataflow test query and the expected results
---
.../Security/CWE-022/DataflowQueryTest.ql | 2 +-
.../Security/CWE-022/UnsafeUnpack.expected | 45 +++----------------
2 files changed, 6 insertions(+), 41 deletions(-)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
index e3cd5f784ff3..df70ff9fe51d 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
@@ -1,3 +1,3 @@
import python
import experimental.dataflow.TestUtil.DataflowQueryTest
-import UnsafeUnpackQuery
\ No newline at end of file
+import experimental.Security.UnsafeUnpackQuery
\ No newline at end of file
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
index cda42c13267c..c07d7f1e245e 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -1,40 +1,5 @@
-edges
-| UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request | UnsafeUnpack.py:11:16:11:22 | ControlFlowNode for request |
-| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:5:26:5:32 | GSSA Variable request |
-| UnsafeUnpack.py:5:26:5:32 | GSSA Variable request | UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request |
-| UnsafeUnpack.py:11:16:11:22 | ControlFlowNode for request | UnsafeUnpack.py:11:16:11:27 | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:11:16:11:27 | ControlFlowNode for Attribute | UnsafeUnpack.py:17:23:17:34 | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:17:23:17:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath |
-| UnsafeUnpack.py:34:50:34:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path |
-| UnsafeUnpack.py:48:20:48:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:52:19:52:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:66:19:66:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:80:16:80:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:86:15:86:26 | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:86:15:86:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath |
-nodes
-| UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request | semmle.label | ModuleVariableNode for UnsafeUnpack.request |
-| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
-| UnsafeUnpack.py:5:26:5:32 | GSSA Variable request | semmle.label | GSSA Variable request |
-| UnsafeUnpack.py:11:16:11:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
-| UnsafeUnpack.py:11:16:11:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:17:23:17:34 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
-| UnsafeUnpack.py:34:50:34:65 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
-| UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
-| UnsafeUnpack.py:48:20:48:34 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:52:19:52:36 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:66:19:66:31 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
-| UnsafeUnpack.py:80:16:80:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:86:15:86:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
-subpaths
-#select
-| UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:20:31:20:37 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:34:50:34:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:35:23:35:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:48:20:48:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:49:23:49:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:52:19:52:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:53:23:53:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:66:19:66:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:67:23:67:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
-| UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:80:16:80:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:88:23:88:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+ERROR: Could not resolve module DataFlow (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:19,8-16)
+ERROR: Could not resolve module DataFlow (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:21,36-44)
+ERROR: Could not resolve module DataFlow (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:21,63-71)
+ERROR: Could not resolve module UnsafeUnpackQuery (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:18,8-25)
+ERROR: Could not resolve type UnsafeUnpackingConfig (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:21,6-27)
From e41042418aad3922897caaaac0ecdb58da102e97 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 13:46:57 +0100
Subject: [PATCH 31/52] Update the import relative to the dataflow config
---
python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
index 1e17cb728cab..41d46218ec68 100644
--- a/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
@@ -15,7 +15,7 @@
*/
import python
-import UnsafeUnpackQuery
+import experimental.Security.UnsafeUnpackQuery
import DataFlow::PathGraph
from UnsafeUnpackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
From 18d8bbc9a4f09be753ad93c8085f767d6f07755d Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 14:05:25 +0100
Subject: [PATCH 32/52] Updated the expected results accordingly
---
.../Security/CWE-022/UnsafeUnpack.expected | 65 +++++++++++++++++--
1 file changed, 60 insertions(+), 5 deletions(-)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
index c07d7f1e245e..eed5e0e45d75 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -1,5 +1,60 @@
-ERROR: Could not resolve module DataFlow (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:19,8-16)
-ERROR: Could not resolve module DataFlow (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:21,36-44)
-ERROR: Could not resolve module DataFlow (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:21,63-71)
-ERROR: Could not resolve module UnsafeUnpackQuery (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:18,8-25)
-ERROR: Could not resolve type UnsafeUnpackingConfig (/home/sim4n6/Desktop/Ghsecuritylab/my-codeql-PR-3/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql:21,6-27)
+edges
+| UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request | UnsafeUnpack.py:11:18:11:24 | ControlFlowNode for request |
+| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:5:26:5:32 | GSSA Variable request |
+| UnsafeUnpack.py:5:26:5:32 | GSSA Variable request | UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request |
+| UnsafeUnpack.py:11:18:11:24 | ControlFlowNode for request | UnsafeUnpack.py:11:18:11:29 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:11:18:11:29 | ControlFlowNode for Attribute | UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:33:50:33:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:47:20:47:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:51:19:51:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:65:19:65:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:103:23:103:27 | SSA variable chunk | UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath |
+| UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:103:32:103:54 | ControlFlowNode for Subscript |
+| UnsafeUnpack.py:103:32:103:54 | ControlFlowNode for Subscript | UnsafeUnpack.py:103:23:103:27 | SSA variable chunk |
+| UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path |
+| UnsafeUnpack.py:116:17:116:21 | SSA variable ufile | UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:116:17:116:21 | SSA variable ufile |
+| UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path |
+nodes
+| UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request | semmle.label | ModuleVariableNode for UnsafeUnpack.request |
+| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
+| UnsafeUnpack.py:5:26:5:32 | GSSA Variable request | semmle.label | GSSA Variable request |
+| UnsafeUnpack.py:11:18:11:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| UnsafeUnpack.py:11:18:11:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:33:50:33:65 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | semmle.label | ControlFlowNode for local_ziped_path |
+| UnsafeUnpack.py:47:20:47:34 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:51:19:51:36 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:65:19:65:31 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | semmle.label | ControlFlowNode for compressed_file |
+| UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | semmle.label | ControlFlowNode for tarpath |
+| UnsafeUnpack.py:103:23:103:27 | SSA variable chunk | semmle.label | SSA variable chunk |
+| UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:103:32:103:54 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | semmle.label | ControlFlowNode for savepath |
+| UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
+| UnsafeUnpack.py:116:17:116:21 | SSA variable ufile | semmle.label | SSA variable ufile |
+| UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | semmle.label | ControlFlowNode for uploaded_file_path |
+subpaths
+#select
+| UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:33:50:33:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:47:20:47:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:51:19:51:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:65:19:65:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
From 207ed3da9c3c4aa52b03c34fcfc20eaaeda44f05 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 15:07:20 +0100
Subject: [PATCH 33/52] Constrain the object & the call
---
.../Security/UnsafeUnpackQuery.qll | 39 ++++++-------------
1 file changed, 11 insertions(+), 28 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index 4e7e97188c0a..182ef30f84b6 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -61,13 +61,6 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
- // Reading the response
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and
- mc.getMethodName() = "read" and
- nodeTo = mc
- )
- or
// Open for access
exists(MethodCallNode cn |
nodeTo = cn.getObject() and
@@ -77,21 +70,20 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
or
// Write for access
exists(MethodCallNode cn |
- nodeFrom = cn.getObject() and
- cn.getMethodName() = "write" and
+ cn.calls(nodeFrom, "write") and
nodeTo = cn.getArg(0)
)
or
// Retrieve Django uploaded files
- // see HttpRequest.FILES.getlist(): https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.QueryDict.getlist
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and
- mc.getMethodName() = ["getlist", "get"] and
- nodeTo = mc
- )
+ // see getlist(): https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.QueryDict.getlist
+ // see chunks(): https://docs.djangoproject.com/en/4.1/ref/files/uploads/#django.core.files.uploadedfile.UploadedFile.chunks
+ nodeTo.(MethodCallNode).calls(nodeFrom, ["getlist", "get", "chunks"])
+ or
+ // Reading the response
+ nodeTo.(MethodCallNode).calls(nodeFrom, "read")
or
// Accessing the name or raw content
- exists(AttrRead ar | ar.accesses(nodeFrom, ["name", "raw"]) and ar.flowsTo(nodeTo))
+ nodeTo.(AttrRead).accesses(nodeFrom, ["name", "raw"])
or
// Considering the use of "fs"
exists(API::CallNode fs, MethodCallNode mcn |
@@ -109,21 +101,12 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
)
or
//Use of join of filename
- exists(API::CallNode mcn |
- mcn = API::moduleImport("os").getMember("path").getMember("join").getACall() and
- nodeFrom = mcn.getArg(1) and
- mcn.flowsTo(nodeTo)
- )
- or
- // Read by chunks
- exists(MethodCallNode mc |
- nodeFrom = mc.getObject() and mc.getMethodName() = "chunks" and mc.flowsTo(nodeTo)
- )
+ nodeTo = API::moduleImport("os").getMember("path").getMember("join").getACall() and
+ nodeFrom = nodeTo.(API::CallNode).getArg(1)
or
// Write access
exists(MethodCallNode cn |
- nodeTo = cn.getObject() and
- cn.getMethodName() = "write" and
+ cn.calls(nodeTo, "write") and
nodeFrom = cn.getArg(0)
)
or
From 8ef2aa00e73814b6c84d40cee53b0a3afdb650c5 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 16:07:39 +0100
Subject: [PATCH 34/52] Update
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
Co-authored-by: yoff
---
.../src/experimental/Security/UnsafeUnpackQuery.qll | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index 182ef30f84b6..c0a370fa9f45 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -32,12 +32,13 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
or
// A source catching an S3 filename download
// see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
- exists(MethodCallNode mcn, Node s3, Node bc |
- bc = API::moduleImport("boto3").getMember("client").getACall() and
- bc = s3.getALocalSource() and
- mcn.calls(s3, "download_file") and
- source = mcn.getArg(2)
- )
+ source =
+ API::moduleImport("boto3")
+ .getMember("client")
+ .getReturn()
+ .getMember("download_file")
+ .getACall()
+ .getArg(2)
or
// A source download a file using wget
// see wget: https://pypi.org/project/wget/
From 0b27b1314af813607a9a4327eced1aeb46b78a04 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 16:12:08 +0100
Subject: [PATCH 35/52] Update
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
Co-authored-by: yoff
---
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll | 1 -
1 file changed, 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index c0a370fa9f45..f660294cb599 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -24,7 +24,6 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
args =
API::moduleImport("argparse")
.getMember("ArgumentParser")
- .getACall()
.getReturn()
.getMember("parse_args")
.getACall()
From ee213123ac76101840c0762b74973fe03c063e1a Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 18:16:11 +0100
Subject: [PATCH 36/52] Add builtin open as an additional step
---
.../src/experimental/Security/UnsafeUnpackQuery.qll | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index f660294cb599..837f84ee21f3 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -61,18 +61,15 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
- // Open for access
+ // Open a file for access
exists(MethodCallNode cn |
nodeTo = cn.getObject() and
cn.getMethodName() = "open" and
cn.flowsTo(nodeFrom)
)
or
- // Write for access
- exists(MethodCallNode cn |
- cn.calls(nodeFrom, "write") and
- nodeTo = cn.getArg(0)
- )
+ // Open a file for access using builtin
+ nodeFrom = API::builtin("open").getACall() and nodeTo = nodeFrom.(API::CallNode).getArg(0)
or
// Retrieve Django uploaded files
// see getlist(): https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.QueryDict.getlist
@@ -100,7 +97,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeTo = mcn
)
or
- //Use of join of filename
+ // Join the base_dir to the filename
nodeTo = API::moduleImport("os").getMember("path").getMember("join").getACall() and
nodeFrom = nodeTo.(API::CallNode).getArg(1)
or
From 0e2f37825ddd56e1dbdd3b131bc4a6e97821cd9c Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 27 Jan 2023 23:58:03 +0100
Subject: [PATCH 37/52] Organize steps to correspond to the sample code
---
.../Security/UnsafeUnpackQuery.qll | 38 +++++++++----------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index 837f84ee21f3..bdac41e8812b 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -61,6 +61,9 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ // Reading the response
+ nodeTo.(MethodCallNode).calls(nodeFrom, "read")
+ or
// Open a file for access
exists(MethodCallNode cn |
nodeTo = cn.getObject() and
@@ -68,6 +71,12 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
cn.flowsTo(nodeFrom)
)
or
+ // Write access
+ exists(MethodCallNode cn |
+ cn.calls(nodeTo, "write") and
+ nodeFrom = cn.getArg(0)
+ )
+ or
// Open a file for access using builtin
nodeFrom = API::builtin("open").getACall() and nodeTo = nodeFrom.(API::CallNode).getArg(0)
or
@@ -76,11 +85,13 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// see chunks(): https://docs.djangoproject.com/en/4.1/ref/files/uploads/#django.core.files.uploadedfile.UploadedFile.chunks
nodeTo.(MethodCallNode).calls(nodeFrom, ["getlist", "get", "chunks"])
or
- // Reading the response
- nodeTo.(MethodCallNode).calls(nodeFrom, "read")
- or
- // Accessing the name or raw content
- nodeTo.(AttrRead).accesses(nodeFrom, ["name", "raw"])
+ // Writing the response data to the archive
+ exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
+ is.flowsTo(f) and
+ mc.calls(f, "write") and
+ nodeFrom = mc.getArg(0) and
+ nodeTo = is.(CallCfgNode).getArg(0)
+ )
or
// Considering the use of "fs"
exists(API::CallNode fs, MethodCallNode mcn |
@@ -97,22 +108,11 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeTo = mcn
)
or
+ // Accessing the name or raw content
+ nodeTo.(AttrRead).accesses(nodeFrom, ["name", "raw"])
+ or
// Join the base_dir to the filename
nodeTo = API::moduleImport("os").getMember("path").getMember("join").getACall() and
nodeFrom = nodeTo.(API::CallNode).getArg(1)
- or
- // Write access
- exists(MethodCallNode cn |
- cn.calls(nodeTo, "write") and
- nodeFrom = cn.getArg(0)
- )
- or
- // Writing the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc.calls(f, "write") and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
}
}
From a4aaf0ec6f9ed100ef1966a37cc65c5fc62553a9 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Sat, 28 Jan 2023 09:53:54 +0100
Subject: [PATCH 38/52] Remove a write step & update the builtin open step
---
.../Security/UnsafeUnpackQuery.qll | 21 +++++++------------
1 file changed, 8 insertions(+), 13 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index bdac41e8812b..d449f5f54334 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -66,8 +66,14 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
or
// Open a file for access
exists(MethodCallNode cn |
- nodeTo = cn.getObject() and
- cn.getMethodName() = "open" and
+ cn.calls(nodeTo, "open") and
+ cn.flowsTo(nodeFrom)
+ )
+ or
+ // Open a file for access using builtin
+ exists(API::CallNode cn |
+ cn = API::builtin("open").getACall() and
+ nodeTo = cn.(API::CallNode).getArg(0) and
cn.flowsTo(nodeFrom)
)
or
@@ -77,22 +83,11 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeFrom = cn.getArg(0)
)
or
- // Open a file for access using builtin
- nodeFrom = API::builtin("open").getACall() and nodeTo = nodeFrom.(API::CallNode).getArg(0)
- or
// Retrieve Django uploaded files
// see getlist(): https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.QueryDict.getlist
// see chunks(): https://docs.djangoproject.com/en/4.1/ref/files/uploads/#django.core.files.uploadedfile.UploadedFile.chunks
nodeTo.(MethodCallNode).calls(nodeFrom, ["getlist", "get", "chunks"])
or
- // Writing the response data to the archive
- exists(Stdlib::FileLikeObject::InstanceSource is, Node f, MethodCallNode mc |
- is.flowsTo(f) and
- mc.calls(f, "write") and
- nodeFrom = mc.getArg(0) and
- nodeTo = is.(CallCfgNode).getArg(0)
- )
- or
// Considering the use of "fs"
exists(API::CallNode fs, MethodCallNode mcn |
fs =
From 0707064ab511390a79b359880c97f6034a95a871 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Sat, 28 Jan 2023 10:14:24 +0100
Subject: [PATCH 39/52] Constrain the save/path step
---
.../Security/UnsafeUnpackQuery.qll | 24 +++++++++----------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index d449f5f54334..99ffb443dca1 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -89,19 +89,17 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeTo.(MethodCallNode).calls(nodeFrom, ["getlist", "get", "chunks"])
or
// Considering the use of "fs"
- exists(API::CallNode fs, MethodCallNode mcn |
- fs =
- API::moduleImport("django")
- .getMember("core")
- .getMember("files")
- .getMember("storage")
- .getMember("FileSystemStorage")
- .getACall() and
- fs.flowsTo(mcn.getObject()) and
- mcn.getMethodName() = ["save", "path"] and
- nodeFrom = mcn.getArg(0) and
- nodeTo = mcn
- )
+ // see fs: https://docs.djangoproject.com/en/4.1/ref/files/storage/#the-filesystemstorage-class
+ nodeTo =
+ API::moduleImport("django")
+ .getMember("core")
+ .getMember("files")
+ .getMember("storage")
+ .getMember("FileSystemStorage")
+ .getReturn()
+ .getMember(["save", "path"])
+ .getACall() and
+ nodeFrom = nodeTo.(MethodCallNode).getArg(0)
or
// Accessing the name or raw content
nodeTo.(AttrRead).accesses(nodeFrom, ["name", "raw"])
From 7079def7ce31920affd1e585a33116c1ba5a5036 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Mon, 30 Jan 2023 00:49:23 +0100
Subject: [PATCH 40/52] Add an S3 source with Session or download_fileobj
---
.../experimental/Security/UnsafeUnpackQuery.qll | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index 99ffb443dca1..7f305a1d734d 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -29,13 +29,25 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
.getACall()
)
or
- // A source catching an S3 filename download
+ // A source catching an S3 file download
// see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.download_file
source =
API::moduleImport("boto3")
.getMember("client")
.getReturn()
- .getMember("download_file")
+ .getMember(["download_file", "download_fileobj"])
+ .getACall()
+ .getArg(2)
+ or
+ // A source catching an S3 file download
+ // see boto3: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html
+ source =
+ API::moduleImport("boto3")
+ .getMember("Session")
+ .getReturn()
+ .getMember("client")
+ .getReturn()
+ .getMember(["download_file", "download_fileobj"])
.getACall()
.getArg(2)
or
From 1a8c9abee232997cd7d90cb10d65b1bc7c905a60 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 2 Feb 2023 21:09:40 +0100
Subject: [PATCH 41/52] Incorporate Sink & Source as steps from TarSlipQry
---
.../Security/UnsafeUnpackQuery.qll | 86 ++++++++++++++++++-
.../Security/CWE-022/UnsafeUnpack.py | 78 ++++++++++++++++-
2 files changed, 161 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index 7f305a1d734d..b43410b14ad2 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -10,6 +10,35 @@ import semmle.python.dataflow.new.TaintTracking
import semmle.python.frameworks.Stdlib
import semmle.python.dataflow.new.RemoteFlowSources
+/**
+ * Handle those three cases of Tarfile opens:
+ * - `tarfile.open()`
+ * - `tarfile.TarFile()`
+ * - `MKtarfile.Tarfile.open()`
+ */
+API::Node tarfileOpen() {
+ result in [
+ API::moduleImport("tarfile").getMember(["open", "TarFile"]),
+ API::moduleImport("tarfile").getMember("TarFile").getASubclass().getMember("open")
+ ]
+}
+
+/**
+ * Handle the previous three cases, plus the use of `closing` in the previous cases
+ */
+class AllTarfileOpens extends API::CallNode {
+ AllTarfileOpens() {
+ this = tarfileOpen().getACall()
+ or
+ exists(API::Node closing, Node arg |
+ closing = API::moduleImport("contextlib").getMember("closing") and
+ this = closing.getACall() and
+ arg = this.getArg(0) and
+ arg = tarfileOpen().getACall()
+ )
+ }
+}
+
class UnsafeUnpackingConfig extends TaintTracking::Configuration {
UnsafeUnpackingConfig() { this = "UnsafeUnpackingConfig" }
@@ -68,8 +97,47 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
}
override predicate isSink(DataFlow::Node sink) {
- // A sink capturing method calls to `unpack_archive`.
- sink = API::moduleImport("shutil").getMember("unpack_archive").getACall().getArg(0)
+ (
+ // A sink capturing method calls to `unpack_archive`.
+ sink = API::moduleImport("shutil").getMember("unpack_archive").getACall().getArg(0)
+ or
+ // A sink capturing method calls to `extractall` without `members` argument.
+ // For a call to `file.extractall` without `members` argument, `file` is considered a sink.
+ exists(MethodCallNode call, AllTarfileOpens atfo |
+ call = atfo.getReturn().getMember("extractall").getACall() and
+ not exists(Node arg | arg = call.getArgByName("members")) and
+ sink = call.getObject()
+ )
+ or
+ // A sink capturing method calls to `extractall` with `members` argument.
+ // For a call to `file.extractall` with `members` argument, `file` is considered a sink if not
+ // a the `members` argument contains a NameConstant as None, a List or call to the method `getmembers`.
+ // Otherwise, the argument of `members` is considered a sink.
+ exists(MethodCallNode call, Node arg, AllTarfileOpens atfo |
+ call = atfo.getReturn().getMember("extractall").getACall() and
+ arg = call.getArgByName("members") and
+ if
+ arg.asCfgNode() instanceof NameConstantNode or
+ arg.asCfgNode() instanceof ListNode
+ then sink = call.getObject()
+ else
+ if arg.(MethodCallNode).getMethodName() = "getmembers"
+ then sink = arg.(MethodCallNode).getObject()
+ else sink = call.getArgByName("members")
+ )
+ or
+ // An argument to `extract` is considered a sink.
+ exists(AllTarfileOpens atfo |
+ sink = atfo.getReturn().getMember("extract").getACall().getArg(0)
+ )
+ or
+ //An argument to `_extract_member` is considered a sink.
+ exists(MethodCallNode call, AllTarfileOpens atfo |
+ call = atfo.getReturn().getMember("_extract_member").getACall() and
+ call.getArg(1).(AttrRead).accesses(sink, "name")
+ )
+ ) and
+ not sink.getScope().getLocation().getFile().inStdlib()
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
@@ -119,5 +187,19 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// Join the base_dir to the filename
nodeTo = API::moduleImport("os").getMember("path").getMember("join").getACall() and
nodeFrom = nodeTo.(API::CallNode).getArg(1)
+ or
+ // Go through an Open for a Tarfile
+ nodeTo = tarfileOpen().getACall() and nodeFrom = nodeTo.(MethodCallNode).getArg(0)
+ or
+ // Handle the case where the getmembers is used.
+ nodeTo.(MethodCallNode).calls(nodeFrom, "getmembers") and
+ nodeFrom instanceof AllTarfileOpens
+ or
+ // To handle the case of `with closing(tarfile.open()) as file:`
+ // we add a step from the first argument of `closing` to the call to `closing`,
+ // whenever that first argument is a return of `tarfile.open()`.
+ nodeTo = API::moduleImport("contextlib").getMember("closing").getACall() and
+ nodeFrom = nodeTo.(API::CallNode).getArg(0) and
+ nodeFrom = tarfileOpen().getReturn().getAValueReachableFromSource()
}
}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
index ae9ee0559992..50574281cbd4 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
@@ -122,4 +122,80 @@ def simple_upload(request):
return render(request, 'simple_upload.html')
elif request.method == 'GET':
- return render(request, 'simple_upload.html')
\ No newline at end of file
+ return render(request, 'simple_upload.html')
+
+
+import shutil
+import os
+import tarfile
+import tempfile
+import argparse
+
+parser = argparse.ArgumentParser(description='Process some integers.')
+parser.add_argument('integers', metavar='N', type=int, nargs='+',
+ help='an integer for the accumulator')
+parser.add_argument('filename', help='filename to be provided')
+
+args = parser.parse_args()
+unsafe_filename_tar = args.filename
+with tarfile.TarFile(unsafe_filename_tar, mode="r") as tar:
+ tar.extractall(path="/tmp/unpack/", members=tar) # $result=BAD
+tar = tarfile.open(unsafe_filename_tar)
+
+
+from django.shortcuts import render
+from django.core.files.storage import FileSystemStorage
+import shutil
+
+def simple_upload(request):
+
+ base_dir = "/tmp/baase_dir"
+ if request.method == 'POST':
+ # Read uploaded files by chunks of data
+ # see chunks(): https://docs.djangoproject.com/en/4.1/ref/files/uploads/#django.core.files.uploadedfile.UploadedFile.chunks
+ savepath = os.path.join(base_dir, "tarball_compressed.tar.gz")
+ with open(savepath, 'wb+') as wfile:
+ for chunk in request.FILES["ufile1"].chunks():
+ wfile.write(chunk)
+
+ tar = tarfile.open(savepath)
+ result = []
+ for member in tar:
+ if member.issym():
+ raise ValueError("But it is a symlink")
+ result.append(member)
+ tar.extractall(path=tempfile.mkdtemp(), members=result) # $result=BAD
+ tar.close()
+
+
+response = requests.get(url_filename, stream=True)
+tarpath = "/tmp/tmp456/tarball.tar.gz"
+with open(tarpath, "wb") as f:
+ f.write(response.raw.read())
+target_dir = "/tmp/unpack"
+tarfile.TarFile(tarpath, mode="r").extractall(path=target_dir) # $result=BAD
+
+
+from pathlib import Path
+import tempfile
+import boto3
+
+def default_session() -> boto3.Session:
+ _SESSION = None
+ if _SESSION is None:
+ _SESSION = boto3.Session()
+ return _SESSION
+
+cache = False
+cache_dir = "/tmp/artifacts"
+object_path = "/objects/obj1"
+s3 = default_session().client("s3")
+with tempfile.NamedTemporaryFile(suffix=".tar.gz") as tmp:
+ s3.download_fileobj(bucket_name, object_path, tmp)
+ tmp.seek(0)
+ if cache:
+ cache_dir.mkdir(exist_ok=True, parents=True)
+ target = cache_dir
+ else:
+ target = Path(tempfile.mkdtemp())
+ shutil.unpack_archive(tmp.name, target) # $result=BAD
\ No newline at end of file
From a0150849cbafae04aaee91d9c6b38c4ef01d4f9d Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 2 Feb 2023 21:42:47 +0100
Subject: [PATCH 42/52] Updated the expected test file
---
.../Security/CWE-022/UnsafeUnpack.expected | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
index eed5e0e45d75..94bd7276631f 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
@@ -10,6 +10,7 @@ edges
| UnsafeUnpack.py:51:19:51:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file |
| UnsafeUnpack.py:65:19:65:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file |
| UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute |
| UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath |
| UnsafeUnpack.py:103:23:103:27 | SSA variable chunk | UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath |
| UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:103:32:103:54 | ControlFlowNode for Subscript |
@@ -18,6 +19,13 @@ edges
| UnsafeUnpack.py:116:17:116:21 | SSA variable ufile | UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute |
| UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:116:17:116:21 | SSA variable ufile |
| UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path |
+| UnsafeUnpack.py:140:23:140:35 | ControlFlowNode for Attribute | UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar |
+| UnsafeUnpack.py:158:23:158:27 | SSA variable chunk | UnsafeUnpack.py:163:23:163:28 | SSA variable member |
+| UnsafeUnpack.py:158:32:158:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:158:32:158:54 | ControlFlowNode for Subscript |
+| UnsafeUnpack.py:158:32:158:54 | ControlFlowNode for Subscript | UnsafeUnpack.py:158:23:158:27 | SSA variable chunk |
+| UnsafeUnpack.py:163:23:163:28 | SSA variable member | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result |
+| UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() |
+| UnsafeUnpack.py:194:53:194:55 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute |
nodes
| UnsafeUnpack.py:0:0:0:0 | ModuleVariableNode for UnsafeUnpack.request | semmle.label | ModuleVariableNode for UnsafeUnpack.request |
| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
@@ -47,6 +55,17 @@ nodes
| UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | semmle.label | ControlFlowNode for uploaded_file_path |
+| UnsafeUnpack.py:140:23:140:35 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | semmle.label | ControlFlowNode for tar |
+| UnsafeUnpack.py:158:23:158:27 | SSA variable chunk | semmle.label | SSA variable chunk |
+| UnsafeUnpack.py:158:32:158:44 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:158:32:158:54 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| UnsafeUnpack.py:163:23:163:28 | SSA variable member | semmle.label | SSA variable member |
+| UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | semmle.label | ControlFlowNode for result |
+| UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| UnsafeUnpack.py:194:53:194:55 | ControlFlowNode for tmp | semmle.label | ControlFlowNode for tmp |
+| UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
subpaths
#select
| UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
@@ -58,3 +77,7 @@ subpaths
| UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | Unsafe extraction from a malicious tarball retrieved from a remote location. |
| UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | UnsafeUnpack.py:140:23:140:35 | ControlFlowNode for Attribute | UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | UnsafeUnpack.py:158:32:158:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | Unsafe extraction from a malicious tarball retrieved from a remote location. |
+| UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | UnsafeUnpack.py:194:53:194:55 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | Unsafe extraction from a malicious tarball retrieved from a remote location. |
From ec82d61991f205bdb43681a82c2fe7dff19d0d37 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Sun, 5 Feb 2023 14:36:17 +0100
Subject: [PATCH 43/52] Add another frequently used step
---
.../experimental/Security/UnsafeUnpackQuery.qll | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index b43410b14ad2..bc8682f9e356 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -190,7 +190,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
or
// Go through an Open for a Tarfile
nodeTo = tarfileOpen().getACall() and nodeFrom = nodeTo.(MethodCallNode).getArg(0)
- or
+ or
// Handle the case where the getmembers is used.
nodeTo.(MethodCallNode).calls(nodeFrom, "getmembers") and
nodeFrom instanceof AllTarfileOpens
@@ -201,5 +201,17 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
nodeTo = API::moduleImport("contextlib").getMember("closing").getACall() and
nodeFrom = nodeTo.(API::CallNode).getArg(0) and
nodeFrom = tarfileOpen().getReturn().getAValueReachableFromSource()
+ or
+ // see Path : https://docs.python.org/3/library/pathlib.html#pathlib.Path
+ nodeTo = API::moduleImport("pathlib").getMember("Path").getACall() and
+ nodeFrom = nodeTo.(API::CallNode).getArg(0)
+ or
+ // Use of absolutepath
+ // see absolute : https://docs.python.org/3/library/pathlib.html#pathlib.Path.absolute
+ exists(API::CallNode mcn |
+ mcn = API::moduleImport("pathlib").getMember("Path").getACall() and
+ nodeTo = mcn.getAMethodCall("absolute") and
+ nodeFrom = mcn.getArg(0)
+ )
}
}
From 9e285020a10c493052a9c87a031294c71d32378c Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Wed, 8 Feb 2023 21:14:53 +0100
Subject: [PATCH 44/52] Comment modif + remove redundant cast
---
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index bc8682f9e356..e2972b446622 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -24,7 +24,7 @@ API::Node tarfileOpen() {
}
/**
- * Handle the previous three cases, plus the use of `closing` in the previous cases
+ * A class for handling the previous three cases, plus the use of `closing` in with the previous cases
*/
class AllTarfileOpens extends API::CallNode {
AllTarfileOpens() {
@@ -153,7 +153,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// Open a file for access using builtin
exists(API::CallNode cn |
cn = API::builtin("open").getACall() and
- nodeTo = cn.(API::CallNode).getArg(0) and
+ nodeTo = cn.getArg(0) and
cn.flowsTo(nodeFrom)
)
or
From 4196230a8a10d83bdad088de13cfa56538576aac Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Wed, 8 Feb 2023 21:46:50 +0100
Subject: [PATCH 45/52] use if-then-else rather than nested exists
---
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index e2972b446622..a1f028134d39 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -84,11 +84,9 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// see wget: https://pypi.org/project/wget/
exists(API::CallNode mcn |
mcn = API::moduleImport("wget").getMember("download").getACall() and
- (
- source = mcn.getArg(1)
- or
- source = mcn.getReturn().asSource() and not exists(Node arg | arg = mcn.getArg(1))
- )
+ if exists(Node arg | arg = mcn.getArg(1))
+ then source = mcn.getArg(1)
+ else source = mcn.getReturn().asSource()
)
or
// catch the Django uploaded files as a source
From 16ef50401ba251df3898c9acad92916e632bd2e9 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 9 Feb 2023 14:59:28 +0100
Subject: [PATCH 46/52] Update
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
Co-authored-by: yoff
---
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index a1f028134d39..a92a2cf2f840 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -103,7 +103,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// For a call to `file.extractall` without `members` argument, `file` is considered a sink.
exists(MethodCallNode call, AllTarfileOpens atfo |
call = atfo.getReturn().getMember("extractall").getACall() and
- not exists(Node arg | arg = call.getArgByName("members")) and
+ not exists(call.getArgByName("members")) and
sink = call.getObject()
)
or
From b04d5684fbabd791ec636f223e03b656279e8aea Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 9 Feb 2023 15:23:58 +0100
Subject: [PATCH 47/52] add a blank line at the end of the file
---
.../query-tests/Security/CWE-022/DataflowQueryTest.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
index df70ff9fe51d..f02f8529c96a 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
@@ -1,3 +1,3 @@
import python
import experimental.dataflow.TestUtil.DataflowQueryTest
-import experimental.Security.UnsafeUnpackQuery
\ No newline at end of file
+import experimental.Security.UnsafeUnpackQuery
From 09df055d86b54b0e1df6facca925dd9f0b8a04be Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Thu, 9 Feb 2023 15:25:54 +0100
Subject: [PATCH 48/52] Fix the exists cast warning
---
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index a92a2cf2f840..022a0fb26451 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -84,7 +84,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// see wget: https://pypi.org/project/wget/
exists(API::CallNode mcn |
mcn = API::moduleImport("wget").getMember("download").getACall() and
- if exists(Node arg | arg = mcn.getArg(1))
+ if exists(mcn.getArg(1))
then source = mcn.getArg(1)
else source = mcn.getReturn().asSource()
)
From eed19a3e15be8e5970f7a62e8f8fc60fca2e2321 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Fri, 10 Feb 2023 21:58:29 +0100
Subject: [PATCH 49/52] Fix autoformatting issues
---
python/ql/src/experimental/Security/UnsafeUnpackQuery.qll | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index 022a0fb26451..ba359ee32f37 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -84,9 +84,7 @@ class UnsafeUnpackingConfig extends TaintTracking::Configuration {
// see wget: https://pypi.org/project/wget/
exists(API::CallNode mcn |
mcn = API::moduleImport("wget").getMember("download").getACall() and
- if exists(mcn.getArg(1))
- then source = mcn.getArg(1)
- else source = mcn.getReturn().asSource()
+ if exists(mcn.getArg(1)) then source = mcn.getArg(1) else source = mcn.getReturn().asSource()
)
or
// catch the Django uploaded files as a source
From 80d4fb5e33c49499bf329e11478e3a338e1ce86b Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Sun, 12 Feb 2023 10:51:53 +0100
Subject: [PATCH 50/52] Organisation TarSlip/UnsafeUnpack into two folders
---
.../TarSlip.expected | 0
.../TarSlip.qlref | 0
.../TarSlipImprov.py | 0
.../ZipSlip.expected | 0
.../ZipSlip.qlref | 0
.../zipslip_bad.py | 0
.../zipslip_good.py | 0
.../DataflowQueryTest.expected | 15 +++++++++++
.../DataflowQueryTest.ql | 0
.../UnsafeUnpack.expected | 0
.../UnsafeUnpack.py | 26 +++++++++----------
.../UnsafeUnpack.qlref | 0
.../CWE-022/DataflowQueryTest.expected | 2 --
13 files changed, 28 insertions(+), 15 deletions(-)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-TarSlip}/TarSlip.expected (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-TarSlip}/TarSlip.qlref (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-TarSlip}/TarSlipImprov.py (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-TarSlip}/ZipSlip.expected (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-TarSlip}/ZipSlip.qlref (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-TarSlip}/zipslip_bad.py (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-TarSlip}/zipslip_good.py (100%)
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-UnsafeUnpacking}/DataflowQueryTest.ql (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-UnsafeUnpacking}/UnsafeUnpack.expected (100%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-UnsafeUnpacking}/UnsafeUnpack.py (89%)
rename python/ql/test/experimental/query-tests/Security/{CWE-022 => CWE-022-UnsafeUnpacking}/UnsafeUnpack.qlref (100%)
delete mode 100644 python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/TarSlip.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/TarSlip.expected
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/TarSlip.expected
rename to python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/TarSlip.expected
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/TarSlip.qlref b/python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/TarSlip.qlref
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/TarSlip.qlref
rename to python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/TarSlip.qlref
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/TarSlipImprov.py b/python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/TarSlipImprov.py
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/TarSlipImprov.py
rename to python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/TarSlipImprov.py
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/ZipSlip.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/ZipSlip.expected
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/ZipSlip.expected
rename to python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/ZipSlip.expected
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/ZipSlip.qlref b/python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/ZipSlip.qlref
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/ZipSlip.qlref
rename to python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/ZipSlip.qlref
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/zipslip_bad.py b/python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/zipslip_bad.py
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/zipslip_bad.py
rename to python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/zipslip_bad.py
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/zipslip_good.py b/python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/zipslip_good.py
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/zipslip_good.py
rename to python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/zipslip_good.py
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected
new file mode 100644
index 000000000000..5a1382ed87c4
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected
@@ -0,0 +1,15 @@
+missingAnnotationOnSink
+failures
+| UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | Unexpected result: result=BAD |
+| UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | Unexpected result: result=BAD |
+| UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | Unexpected result: result=BAD |
+| UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | Unexpected result: result=BAD |
+| UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | Unexpected result: result=BAD |
+| UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | Unexpected result: result=BAD |
+| UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | Unexpected result: result=BAD |
+| UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | Unexpected result: result=BAD |
+| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | Unexpected result: result=BAD |
+| UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | Unexpected result: result=BAD |
+| UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | Unexpected result: result=BAD |
+| UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | Unexpected result: result=BAD |
+| UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | Unexpected result: result=BAD |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.ql
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.ql
rename to python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.ql
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.expected
rename to python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.py
similarity index 89%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
rename to python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.py
index 50574281cbd4..c83714b764c4 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.py
@@ -16,7 +16,7 @@ def download_from_url():
with open(tarpath, "wb") as f:
f.write(response.raw.read())
untarredpath = "/tmp/tmp123"
- shutil.unpack_archive(tarpath, untarredpath) # $result=BAD
+ shutil.unpack_archive(tarpath, untarredpath)
# A source catching an S3 filename download
@@ -31,7 +31,7 @@ def download_from_url():
s3 = boto3.client('s3')
s3.download_file(bucket_name, remote_ziped_name, local_ziped_path)
-shutil.unpack_archive(local_ziped_path, base_dir) # $result=BAD
+shutil.unpack_archive(local_ziped_path, base_dir)
# wget
@@ -45,11 +45,11 @@ def download_from_url():
# download(url, out, bar) contains out parameter
wget.download(url, compressed_file)
-shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
+shutil.unpack_archive(compressed_file, base_dir)
# download(url) returns filename
compressed_file = wget.download(url)
-shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
+shutil.unpack_archive(compressed_file, base_dir)
# A source coming from a CLI argparse module
@@ -63,7 +63,7 @@ def download_from_url():
args = parser.parse_args()
compressed_file = args.filename
-shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
+shutil.unpack_archive(compressed_file, base_dir)
# A source coming from a CLI and downloaded
@@ -84,7 +84,7 @@ def download_from_url():
with open(tarpath, "wb") as f:
f.write(response.raw.read())
-shutil.unpack_archive(tarpath, base_dir) # $result=BAD
+shutil.unpack_archive(tarpath, base_dir)
# the django upload functionality
# see HttpRequest.FILES: https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.HttpRequest.FILES
@@ -102,14 +102,14 @@ def simple_upload(request):
with open(savepath, 'wb+') as wfile:
for chunk in request.FILES["ufile1"].chunks():
wfile.write(chunk)
- shutil.unpack_archive(savepath, base_dir) # $result=BAD
+ shutil.unpack_archive(savepath, base_dir)
# Write in binary the uploaded tarball
myfile = request.FILES.get("ufile1")
file_path = os.path.join(base_dir, "tarball.tar")
with file_path.open('wb') as f:
f.write(myfile.read())
- shutil.unpack_archive(file_path, base_dir) # $result=BAD
+ shutil.unpack_archive(file_path, base_dir)
# Save uploaded files using FileSystemStorage Django API
# see FileSystemStorage: https://docs.djangoproject.com/en/4.1/ref/files/storage/#django.core.files.storage.FileSystemStorage
@@ -117,7 +117,7 @@ def simple_upload(request):
fs = FileSystemStorage()
filename = fs.save(ufile.name, ufile)
uploaded_file_path = fs.path(filename)
- shutil.unpack_archive(uploaded_file_path, base_dir) # $result=BAD
+ shutil.unpack_archive(uploaded_file_path, base_dir)
return render(request, 'simple_upload.html')
@@ -139,7 +139,7 @@ def simple_upload(request):
args = parser.parse_args()
unsafe_filename_tar = args.filename
with tarfile.TarFile(unsafe_filename_tar, mode="r") as tar:
- tar.extractall(path="/tmp/unpack/", members=tar) # $result=BAD
+ tar.extractall(path="/tmp/unpack/", members=tar)
tar = tarfile.open(unsafe_filename_tar)
@@ -164,7 +164,7 @@ def simple_upload(request):
if member.issym():
raise ValueError("But it is a symlink")
result.append(member)
- tar.extractall(path=tempfile.mkdtemp(), members=result) # $result=BAD
+ tar.extractall(path=tempfile.mkdtemp(), members=result)
tar.close()
@@ -173,7 +173,7 @@ def simple_upload(request):
with open(tarpath, "wb") as f:
f.write(response.raw.read())
target_dir = "/tmp/unpack"
-tarfile.TarFile(tarpath, mode="r").extractall(path=target_dir) # $result=BAD
+tarfile.TarFile(tarpath, mode="r").extractall(path=target_dir)
from pathlib import Path
@@ -198,4 +198,4 @@ def default_session() -> boto3.Session:
target = cache_dir
else:
target = Path(tempfile.mkdtemp())
- shutil.unpack_archive(tmp.name, target) # $result=BAD
\ No newline at end of file
+ shutil.unpack_archive(tmp.name, target)
\ No newline at end of file
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.qlref b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.qlref
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-022/UnsafeUnpack.qlref
rename to python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.qlref
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected b/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
deleted file mode 100644
index 3875da4e143c..000000000000
--- a/python/ql/test/experimental/query-tests/Security/CWE-022/DataflowQueryTest.expected
+++ /dev/null
@@ -1,2 +0,0 @@
-missingAnnotationOnSink
-failures
From 518684b73685f4529587de9b5af8acb2450f8046 Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Sun, 12 Feb 2023 21:26:12 +0100
Subject: [PATCH 51/52] Put back the annotation result=BAD
---
.../CWE-022-UnsafeUnpacking/UnsafeUnpack.py | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.py b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.py
index c83714b764c4..6b533462d23c 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.py
@@ -16,7 +16,7 @@ def download_from_url():
with open(tarpath, "wb") as f:
f.write(response.raw.read())
untarredpath = "/tmp/tmp123"
- shutil.unpack_archive(tarpath, untarredpath)
+ shutil.unpack_archive(tarpath, untarredpath) # $result=BAD
# A source catching an S3 filename download
@@ -31,7 +31,7 @@ def download_from_url():
s3 = boto3.client('s3')
s3.download_file(bucket_name, remote_ziped_name, local_ziped_path)
-shutil.unpack_archive(local_ziped_path, base_dir)
+shutil.unpack_archive(local_ziped_path, base_dir) # $result=BAD
# wget
@@ -45,11 +45,11 @@ def download_from_url():
# download(url, out, bar) contains out parameter
wget.download(url, compressed_file)
-shutil.unpack_archive(compressed_file, base_dir)
+shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
# download(url) returns filename
compressed_file = wget.download(url)
-shutil.unpack_archive(compressed_file, base_dir)
+shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
# A source coming from a CLI argparse module
@@ -63,7 +63,7 @@ def download_from_url():
args = parser.parse_args()
compressed_file = args.filename
-shutil.unpack_archive(compressed_file, base_dir)
+shutil.unpack_archive(compressed_file, base_dir) # $result=BAD
# A source coming from a CLI and downloaded
@@ -84,7 +84,7 @@ def download_from_url():
with open(tarpath, "wb") as f:
f.write(response.raw.read())
-shutil.unpack_archive(tarpath, base_dir)
+shutil.unpack_archive(tarpath, base_dir) # $result=BAD
# the django upload functionality
# see HttpRequest.FILES: https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.HttpRequest.FILES
@@ -102,14 +102,14 @@ def simple_upload(request):
with open(savepath, 'wb+') as wfile:
for chunk in request.FILES["ufile1"].chunks():
wfile.write(chunk)
- shutil.unpack_archive(savepath, base_dir)
+ shutil.unpack_archive(savepath, base_dir) # $result=BAD
# Write in binary the uploaded tarball
myfile = request.FILES.get("ufile1")
file_path = os.path.join(base_dir, "tarball.tar")
with file_path.open('wb') as f:
f.write(myfile.read())
- shutil.unpack_archive(file_path, base_dir)
+ shutil.unpack_archive(file_path, base_dir) # $result=BAD
# Save uploaded files using FileSystemStorage Django API
# see FileSystemStorage: https://docs.djangoproject.com/en/4.1/ref/files/storage/#django.core.files.storage.FileSystemStorage
@@ -117,7 +117,7 @@ def simple_upload(request):
fs = FileSystemStorage()
filename = fs.save(ufile.name, ufile)
uploaded_file_path = fs.path(filename)
- shutil.unpack_archive(uploaded_file_path, base_dir)
+ shutil.unpack_archive(uploaded_file_path, base_dir) # $result=BAD
return render(request, 'simple_upload.html')
@@ -139,7 +139,7 @@ def simple_upload(request):
args = parser.parse_args()
unsafe_filename_tar = args.filename
with tarfile.TarFile(unsafe_filename_tar, mode="r") as tar:
- tar.extractall(path="/tmp/unpack/", members=tar)
+ tar.extractall(path="/tmp/unpack/", members=tar) # $result=BAD
tar = tarfile.open(unsafe_filename_tar)
@@ -164,7 +164,7 @@ def simple_upload(request):
if member.issym():
raise ValueError("But it is a symlink")
result.append(member)
- tar.extractall(path=tempfile.mkdtemp(), members=result)
+ tar.extractall(path=tempfile.mkdtemp(), members=result) # $result=BAD
tar.close()
@@ -173,7 +173,7 @@ def simple_upload(request):
with open(tarpath, "wb") as f:
f.write(response.raw.read())
target_dir = "/tmp/unpack"
-tarfile.TarFile(tarpath, mode="r").extractall(path=target_dir)
+tarfile.TarFile(tarpath, mode="r").extractall(path=target_dir) # $result=BAD
from pathlib import Path
@@ -198,4 +198,4 @@ def default_session() -> boto3.Session:
target = cache_dir
else:
target = Path(tempfile.mkdtemp())
- shutil.unpack_archive(tmp.name, target)
\ No newline at end of file
+ shutil.unpack_archive(tmp.name, target) # $result=BAD
\ No newline at end of file
From d7af80136e685141b9ec26286f081bd7bab2cded Mon Sep 17 00:00:00 2001
From: Sim4n6
Date: Sun, 12 Feb 2023 21:27:20 +0100
Subject: [PATCH 52/52] Fail tests when missing annotation on sink orfail
---
.../DataflowQueryTest.expected | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected
index 5a1382ed87c4..3875da4e143c 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/DataflowQueryTest.expected
@@ -1,15 +1,2 @@
missingAnnotationOnSink
failures
-| UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | Unexpected result: result=BAD |
-| UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | Unexpected result: result=BAD |
-| UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | Unexpected result: result=BAD |
-| UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | Unexpected result: result=BAD |
-| UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | Unexpected result: result=BAD |
-| UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | Unexpected result: result=BAD |
-| UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | Unexpected result: result=BAD |
-| UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | Unexpected result: result=BAD |
-| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | Unexpected result: result=BAD |
-| UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | Unexpected result: result=BAD |
-| UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | Unexpected result: result=BAD |
-| UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | Unexpected result: result=BAD |
-| UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | Unexpected result: result=BAD |