Repository navigation
Python: Add TarSlip Improv query #10887
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d6fa745
Add TarSlip Improv query
Sim4n6 25a7fcf
Add an additional taint step
Sim4n6 9163cbe
Restrict the reach for an additional taint step
Sim4n6 925f9d0
Update python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql
Sim4n6 31a6fb4
Add TarSlip qlref for query-tests
Sim4n6 7319052
Delete the examples/
Sim4n6 7d60f1f
Modified the QL ref file and add TarSlip examples
Sim4n6 0f44268
Add expected test results
Sim4n6 fdbed2a
Add expected test results without considering inStdLib files.
Sim4n6 92a3846
Fix query to omit sinks within std lib files
Sim4n6 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
python/ql/src/experimental/Security/CWE-022bis/TarSlip.qhelp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| <!DOCTYPE qhelp PUBLIC | ||
| "-//Semmle//qhelp//EN" | ||
| "qhelp.dtd"> | ||
| <qhelp> | ||
|
|
||
| <overview> | ||
| <p>Extracting files from a malicious tarball without validating that the destination file path | ||
| is within the destination directory can cause files outside the destination directory to be | ||
| overwritten, due to the possible presence of directory traversal elements (<code>..</code>) in | ||
| archive path names.</p> | ||
|
|
||
| <p>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 (<code>..</code>). 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.</p> | ||
|
|
||
| <p>For example, if a tarball contains a file entry <code>../sneaky-file</code>, and the tarball | ||
| is extracted to the directory <code>/tmp/tmp123</code>, then naively combining the paths would result | ||
| in an output file path of <code>/tmp/tmp123/../sneaky-file</code>, which would cause the file to be | ||
| written to <code>/tmp/</code>.</p> | ||
|
|
||
| </overview> | ||
| <recommendation> | ||
|
|
||
| <p>Ensure that output paths constructed from tarball entries are validated | ||
| to prevent writing files to unexpected locations.</p> | ||
|
|
||
| <p>The recommended way of writing an output file from a tarball entry is to call <code>extract()</code> or <code>extractall()</code>. | ||
| </p> | ||
|
|
||
| </recommendation> | ||
|
|
||
| <example> | ||
| <p> | ||
| In this example an archive is extracted without validating file paths. | ||
| </p> | ||
|
|
||
| <sample src="examples/TarSlip_1.py" /> | ||
|
|
||
| <p>To fix this vulnerability, we need to call the function <code>extractall()</code>. | ||
| </p> | ||
|
|
||
| <sample src="examples/NoHIT_TarSlip_1.py" /> | ||
|
|
||
| </example> | ||
| <references> | ||
| <li> | ||
| Snyk: | ||
| <a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>. | ||
| </li> | ||
|
|
||
| <li> | ||
| Tarfile documentation | ||
| <a href="https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall">extractall() warning</a> | ||
| </li> | ||
| </references> | ||
| </qhelp> |
122 changes: 122 additions & 0 deletions
122
python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| /** | ||
| * @name Arbitrary file write during tarfile extraction | ||
| * @description Extracting files from a malicious tar archive without validating that the | ||
| * destination file path is within the destination directory can cause files outside | ||
| * the destination directory to be overwritten. | ||
| * @kind path-problem | ||
| * @id py/tarslip | ||
| * @problem.severity error | ||
| * @security-severity 7.5 | ||
| * @precision high | ||
| * @tags security | ||
| * external/cwe/cwe-022 | ||
| */ | ||
|
|
||
| import python | ||
| import semmle.python.dataflow.new.DataFlow | ||
| import semmle.python.dataflow.new.TaintTracking | ||
| import DataFlow::PathGraph | ||
| import semmle.python.ApiGraphs | ||
| import semmle.python.dataflow.new.internal.Attributes | ||
| import semmle.python.dataflow.new.BarrierGuards | ||
| 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() | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A taint-tracking configuration for detecting more "TarSlip" vulnerabilities. | ||
| */ | ||
| class Configuration extends TaintTracking::Configuration { | ||
| Configuration() { this = "TarSlip" } | ||
|
|
||
| override predicate isSource(DataFlow::Node source) { source = tarfileOpen().getACall() } | ||
|
|
||
| override predicate isSink(DataFlow::Node sink) { | ||
| ( | ||
| // 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) { | ||
| exists(AttrRead attr, MethodCallNode call | | ||
| attr.accesses(nodeFrom, "getmembers") and | ||
| nodeFrom = call.getObject() and | ||
| nodeFrom instanceof AllTarfileOpens and | ||
| nodeTo = call | ||
| ) | ||
| or | ||
| exists(API::CallNode closing | | ||
| closing = API::moduleImport("contextlib").getMember("closing").getACall() and | ||
| nodeFrom = closing.getArg(0) and | ||
| nodeFrom = tarfileOpen().getReturn().getAValueReachingSink() and | ||
| nodeTo = closing | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink | ||
| where config.hasFlowPath(source, sink) | ||
| select sink, source, sink, "Extraction of tarfile from $@ to a potentially untrusted source $@.", | ||
| source.getNode(), source.getNode().toString(), sink.getNode(), sink.getNode().toString() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.