Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8dcd871

Browse files
author
Max Schaefer
authored
Merge pull request #889 from jcreedcmu/jcreed/tarslip
JavaScript: Add new query for ZipSlip (CWE-022).
2 parents 28304e4 + 86bbb5f commit 8dcd871

21 files changed

Lines changed: 286 additions & 0 deletions

File tree

javascript/config/suites/javascript/security

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
+ semmlecode-javascript-queries/Security/CWE-020/IncompleteUrlSubstringSanitization.ql: /Security/CWE/CWE-020
55
+ semmlecode-javascript-queries/Security/CWE-020/IncorrectSuffixCheck.ql: /Security/CWE/CWE-020
66
+ semmlecode-javascript-queries/Security/CWE-022/TaintedPath.ql: /Security/CWE/CWE-022
7+
+ semmlecode-javascript-queries/Security/CWE-022/ZipSlip.ql: /Security/CWE/CWE-022
78
+ semmlecode-javascript-queries/Security/CWE-078/CommandInjection.ql: /Security/CWE/CWE-078
89
+ semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079
910
+ semmlecode-javascript-queries/Security/CWE-079/StoredXss.ql: /Security/CWE/CWE-079
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>Extracting files from a malicious zip archive without validating that the destination file path
8+
is within the destination directory can cause files outside the destination directory to be
9+
overwritten, due to the possible presence of directory traversal elements (<code>..</code>) in
10+
archive paths.</p>
11+
12+
<p>Zip archives contain archive entries representing each file in the archive. These entries
13+
include a file path for the entry, but these file paths are not restricted and may contain
14+
unexpected special elements such as the directory traversal element (<code>..</code>). If these
15+
file paths are used to determine an output file to write the contents of the archive item to, then
16+
the file may be written to an unexpected location. This can result in sensitive information being
17+
revealed or deleted, or an attacker being able to influence behavior by modifying unexpected
18+
files.</p>
19+
20+
<p>For example, if a zip file contains a file entry <code>..\sneaky-file</code>, and the zip file
21+
is extracted to the directory <code>c:\output</code>, then naively combining the paths would result
22+
in an output file path of <code>c:\output\..\sneaky-file</code>, which would cause the file to be
23+
written to <code>c:\sneaky-file</code>.</p>
24+
25+
</overview>
26+
<recommendation>
27+
28+
<p>Ensure that output paths constructed from zip archive entries are validated
29+
to prevent writing files to unexpected locations.</p>
30+
31+
<p>The recommended way of writing an output file from a zip archive entry is to check that
32+
<code>".."</code> does not occur in the path.
33+
</p>
34+
35+
</recommendation>
36+
37+
<example>
38+
<p>
39+
In this example an archive is extracted without validating file paths.
40+
If <code>archive.zip</code> contained relative paths (for
41+
instance, if it were created by something like <code>zip archive.zip
42+
../file.txt</code>) then executing this code could write to locations
43+
outside the destination directory.
44+
</p>
45+
46+
<sample src="ZipSlipBad.js" />
47+
48+
<p>To fix this vulnerability, we need to check that the path does not
49+
contain any <code>".."</code> elements in it.
50+
</p>
51+
52+
<sample src="ZipSlipGood.js" />
53+
54+
</example>
55+
<references>
56+
57+
<li>
58+
Snyk:
59+
<a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>.
60+
</li>
61+
<li>
62+
OWASP:
63+
<a href="https://www.owasp.org/index.php/Path_traversal">Path Traversal</a>.
64+
</li>
65+
66+
</references>
67+
</qhelp>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @name Arbitrary file write during zip extraction ("Zip Slip")
3+
* @description Extracting files from a malicious zip archive without validating that the
4+
* destination file path is within the destination directory can cause files outside
5+
* the destination directory to be overwritten.
6+
* @kind path-problem
7+
* @id js/zipslip
8+
* @problem.severity error
9+
* @precision medium
10+
* @tags security
11+
* external/cwe/cwe-022
12+
*/
13+
14+
import javascript
15+
import semmle.javascript.security.dataflow.ZipSlip::ZipSlip
16+
import DataFlow::PathGraph
17+
18+
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
19+
where cfg.hasFlowPath(source, sink)
20+
select sink.getNode(), source, sink,
21+
"Unsanitized zip archive $@, which may contain '..', is used in a file system operation.",
22+
source.getNode(), "item path"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const fs = require('fs');
2+
const unzip = require('unzip');
3+
4+
fs.createReadStream('archive.zip')
5+
.pipe(unzip.Parse())
6+
.on('entry', entry => {
7+
const fileName = entry.path;
8+
// BAD: This could write any file on the filesystem.
9+
entry.pipe(fs.createWriteStream(fileName));
10+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const fs = require('fs');
2+
const unzip = require('unzip');
3+
4+
fs.createReadStream('archive.zip')
5+
.pipe(unzip.Parse())
6+
.on('entry', entry => {
7+
const fileName = entry.path;
8+
// GOOD: ensures the path is safe to write to.
9+
if (fileName.indexOf('..') == -1) {
10+
entry.pipe(fs.createWriteStream(fileName));
11+
}
12+
else {
13+
console.log('skipping bad path', fileName);
14+
}
15+
});
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* Provides a taint tracking configuration for reasoning about unsafe zip extraction.
3+
*/
4+
5+
import javascript
6+
7+
module ZipSlip {
8+
/**
9+
* A data flow source for unsafe zip extraction.
10+
*/
11+
abstract class Source extends DataFlow::Node { }
12+
13+
/**
14+
* A data flow sink for unsafe zip extraction.
15+
*/
16+
abstract class Sink extends DataFlow::Node { }
17+
18+
/**
19+
* A sanitizer guard for unsafe zip extraction.
20+
*/
21+
abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { }
22+
23+
/** A taint tracking configuration for unsafe zip extraction. */
24+
class Configuration extends TaintTracking::Configuration {
25+
Configuration() { this = "ZipSlip" }
26+
27+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
28+
29+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
30+
31+
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
32+
nd instanceof SanitizerGuard
33+
}
34+
}
35+
36+
/**
37+
* Gets a node that can be a parsed zip archive.
38+
*/
39+
private DataFlow::SourceNode parsedArchive() {
40+
result = DataFlow::moduleImport("unzip").getAMemberCall("Parse")
41+
or
42+
// `streamProducer.pipe(unzip.Parse())` is a typical (but not
43+
// universal) pattern when using nodejs streams, whose return
44+
// value is the parsed stream.
45+
exists(DataFlow::MethodCallNode pipe |
46+
pipe = result and
47+
pipe.getMethodName() = "pipe" and
48+
parsedArchive().flowsTo(pipe.getArgument(0))
49+
)
50+
}
51+
52+
/** A zip archive entry path access, as a source for unsafe zip extraction. */
53+
class UnzipEntrySource extends Source {
54+
// For example, in
55+
// ```javascript
56+
// const unzip = require('unzip');
57+
//
58+
// fs.createReadStream('archive.zip')
59+
// .pipe(unzip.Parse())
60+
// .on('entry', entry => {
61+
// const path = entry.path;
62+
// });
63+
// ```
64+
// there is an `UnzipEntrySource` node corresponding to
65+
// the expression `entry.path`.
66+
UnzipEntrySource() {
67+
exists(DataFlow::CallNode cn |
68+
cn = parsedArchive().getAMemberCall("on") and
69+
cn.getArgument(0).mayHaveStringValue("entry") and
70+
this = cn.getCallback(1)
71+
.getParameter(0)
72+
.getAPropertyRead("path"))
73+
}
74+
}
75+
76+
/** A call to `fs.createWriteStream`, as a sink for unsafe zip extraction. */
77+
class CreateWriteStreamSink extends Sink {
78+
CreateWriteStreamSink() {
79+
// This is not covered by `FileSystemWriteSink`, because it is
80+
// required that a write actually takes place to the stream.
81+
// However, we want to consider even the bare `createWriteStream`
82+
// to be a zipslip vulnerability since it may truncate an
83+
// existing file.
84+
this = DataFlow::moduleImport("fs").getAMemberCall("createWriteStream").getArgument(0)
85+
}
86+
}
87+
88+
/** A file path of a file write, as a sink for unsafe zip extraction. */
89+
class FileSystemWriteSink extends Sink {
90+
FileSystemWriteSink() { exists(FileSystemWriteAccess fsw | fsw.getAPathArgument() = this) }
91+
}
92+
93+
/**
94+
* Gets a string which is sufficient to exclude to make
95+
* a filepath definitely not refer to parent directories.
96+
*/
97+
private string getAParentDirName() { result = ".." or result = "../" }
98+
99+
/** A check that a path string does not include '..' */
100+
class NoParentDirSanitizerGuard extends SanitizerGuard {
101+
StringOps::Includes incl;
102+
103+
NoParentDirSanitizerGuard() { this = incl }
104+
105+
override predicate sanitizes(boolean outcome, Expr e) {
106+
incl.getPolarity().booleanNot() = outcome and
107+
incl.getBaseString().asExpr() = e and
108+
incl.getSubstring().mayHaveStringValue(getAParentDirName())
109+
}
110+
}
111+
}

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath-es6.js renamed to javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath-es6.js

File renamed without changes.

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath.expected renamed to javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected

File renamed without changes.

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath.js renamed to javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js

File renamed without changes.

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath.qlref renamed to javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.qlref

File renamed without changes.

0 commit comments

Comments
 (0)