Merge branch 'main' into promote-xxe

This commit is contained in:
Rasmus Wriedt Larsen
2022-04-20 13:42:02 +02:00
307 changed files with 6388 additions and 16068 deletions

View File

@@ -0,0 +1,56 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Extracting files from a malicious zip archive 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 paths.</p>
<p>Zip archives 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 Zip archive contains a file entry <code>..\sneaky-file</code>, and the Zip archive
is extracted to the directory <code>c:\output</code>, then naively combining the paths would result
in an output file path of <code>c:\output\..\sneaky-file</code>, which would cause the file to be
written to <code>c:\sneaky-file</code>.</p>
</overview>
<recommendation>
<p>Ensure that output paths constructed from Zip archive entries are validated
to prevent writing files to unexpected locations.</p>
<p>The recommended way of writing an output file from a Zip archive 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="zipslip_bad.py" />
<p>To fix this vulnerability, we need to call the function <code>extractall()</code>.
</p>
<sample src="zipslip_good.py" />
</example>
<references>
<li>
Snyk:
<a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name Arbitrary file write during archive extraction ("Zip Slip")
* @description Extracting files from a malicious 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/zipslip
* @problem.severity error
* @security-severity 7.5
* @precision high
* @tags security
* external/cwe/cwe-022
*/
import python
import experimental.semmle.python.security.ZipSlip
import DataFlow::PathGraph
from ZipSlipConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Extraction of zipfile from $@", source.getNode(),
"a potentially untrusted source"

View File

@@ -0,0 +1,16 @@
import zipfile
import shutil
def unzip(filename):
with tarfile.open(filename) as zipf:
#BAD : This could write any file on the filesystem.
for entry in zipf:
shutil.copyfile(entry, "/tmp/unpack/")
def unzip4(filename):
zf = zipfile.ZipFile(filename)
filelist = zf.namelist()
for x in filelist:
with zf.open(x) as srcf:
shutil.copyfileobj(srcf, dstfile)

View File

@@ -0,0 +1,10 @@
import zipfile
def unzip(filename, dir):
zf = zipfile.ZipFile(filename)
zf.extractall(dir)
def unzip1(filename, dir):
zf = zipfile.ZipFile(filename)
zf.extract(dir)

View File

@@ -14,6 +14,73 @@ private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
/** Provides classes for modeling copying file related APIs. */
module CopyFile {
/**
* A data flow node for copying file.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `CopyFile` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the path.
*/
abstract DataFlow::Node getAPathArgument();
/**
* Gets fsrc argument.
*/
abstract DataFlow::Node getfsrcArgument();
}
}
/**
* A data flow node for copying file.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `CopyFile::Range` instead.
*/
class CopyFile extends DataFlow::Node {
CopyFile::Range range;
CopyFile() { this = range }
DataFlow::Node getAPathArgument() { result = range.getAPathArgument() }
DataFlow::Node getfsrcArgument() { result = range.getfsrcArgument() }
}
/** Provides classes for modeling log related APIs. */
module LogOutput {
/**
* A data flow node for log output.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `LogOutput` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Get the parameter value of the log output function.
*/
abstract DataFlow::Node getAnInput();
}
}
/**
* A data flow node for log output.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LogOutput::Range` instead.
*/
class LogOutput extends DataFlow::Node {
LogOutput::Range range;
LogOutput() { this = range }
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
/** Provides classes for modeling LDAP query execution-related APIs. */
module LdapQuery {
/**

View File

@@ -13,3 +13,4 @@ private import experimental.semmle.python.libraries.PyJWT
private import experimental.semmle.python.libraries.Python_JWT
private import experimental.semmle.python.libraries.Authlib
private import experimental.semmle.python.libraries.PythonJose
private import experimental.semmle.python.frameworks.CopyFile

View File

@@ -0,0 +1,42 @@
private import python
private import experimental.semmle.python.Concepts
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.ApiGraphs
private module CopyFileImpl {
/**
* The `shutil` module provides methods to copy or move files.
* See:
* - https://docs.python.org/3/library/shutil.html#shutil.copyfile
* - https://docs.python.org/3/library/shutil.html#shutil.copy
* - https://docs.python.org/3/library/shutil.html#shutil.copy2
* - https://docs.python.org/3/library/shutil.html#shutil.copytree
* - https://docs.python.org/3/library/shutil.html#shutil.move
*/
private class CopyFiles extends DataFlow::CallCfgNode, CopyFile::Range {
CopyFiles() {
this =
API::moduleImport("shutil")
.getMember(["copyfile", "copy", "copy2", "copytree", "move"])
.getACall()
}
override DataFlow::Node getAPathArgument() {
result in [this.getArg(0), this.getArgByName("src")]
}
override DataFlow::Node getfsrcArgument() { none() }
}
// TODO: once we have flow summaries, model `shutil.copyfileobj` which copies the content between its' file-like arguments.
// See https://docs.python.org/3/library/shutil.html#shutil.copyfileobj
private class CopyFileobj extends DataFlow::CallCfgNode, CopyFile::Range {
CopyFileobj() { this = API::moduleImport("shutil").getMember("copyfileobj").getACall() }
override DataFlow::Node getfsrcArgument() {
result in [this.getArg(0), this.getArgByName("fsrc")]
}
override DataFlow::Node getAPathArgument() { none() }
}
}

View File

@@ -0,0 +1,39 @@
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
class ZipSlipConfig extends TaintTracking::Configuration {
ZipSlipConfig() { this = "ZipSlipConfig" }
override predicate isSource(DataFlow::Node source) {
(
source =
API::moduleImport("zipfile").getMember("ZipFile").getReturn().getMember("open").getACall() or
source =
API::moduleImport("zipfile")
.getMember("ZipFile")
.getReturn()
.getMember("namelist")
.getACall() or
source = API::moduleImport("tarfile").getMember("open").getACall() or
source = API::moduleImport("tarfile").getMember("TarFile").getACall() or
source = API::moduleImport("bz2").getMember("open").getACall() or
source = API::moduleImport("bz2").getMember("BZ2File").getACall() or
source = API::moduleImport("gzip").getMember("GzipFile").getACall() or
source = API::moduleImport("gzip").getMember("open").getACall() or
source = API::moduleImport("lzma").getMember("open").getACall() or
source = API::moduleImport("lzma").getMember("LZMAFile").getACall()
) and
not source.getScope().getLocation().getFile().inStdlib()
}
override predicate isSink(DataFlow::Node sink) {
(
sink = any(CopyFile copyfile).getAPathArgument() or
sink = any(CopyFile copyfile).getfsrcArgument()
) and
not sink.getScope().getLocation().getFile().inStdlib()
}
}