Skip to content

Commit 23532ae

Browse files
authored
Merge pull request github#3467 from erik-krogh/tarSlip
Approved by esbena
2 parents 57f44c5 + b12e21e commit 23532ae

File tree

7 files changed

+41
-2
lines changed

7 files changed

+41
-2
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
| Expression has no effect (`js/useless-expression`) | Less results | This query no longer flags an expression when that expression is the only content of the containing file. |
2727
| Unknown directive (`js/unknown-directive`) | Less results | This query no longer flags directives generated by the Babel compiler. |
2828
| Code injection (`js/code-injection`) | More results | More potential vulnerabilities involving NoSQL code operators are now recognized. |
29+
| Zip Slip (`js/zipslip`) | More results | This query now recognizes zip-slip vulnerabilities involving links. |
2930

3031
## Changes to libraries
3132

javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,9 @@ module NodeJSLib {
479479
methodName = "write" or
480480
methodName = "writeFile" or
481481
methodName = "writeFileSync" or
482-
methodName = "writeSync"
482+
methodName = "writeSync" or
483+
methodName = "link" or
484+
methodName = "linkSync"
483485
}
484486

485487
override DataFlow::Node getADataNode() {

javascript/ql/src/semmle/javascript/security/dataflow/ZipSlipCustomizations.qll

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@ module ZipSlip {
4747
)
4848
}
4949

50-
/** Gets a property that is used to get the filename part of an archive entry. */
50+
/** Gets a property that is used to get a filename part of an archive entry. */
5151
private string getAFilenameProperty() {
5252
result = "path" // Used by library 'unzip'.
5353
or
5454
result = "name" // Used by library 'tar-stream'.
55+
or
56+
result = "linkname" // linked file name, used by 'tar-stream'.
5557
}
5658

5759
/** An archive entry path access, as a source for unsafe archive extraction. */
@@ -119,6 +121,20 @@ module ZipSlip {
119121
BasenameSanitizer() { this = DataFlow::moduleImport("path").getAMemberCall("basename") }
120122
}
121123

124+
/**
125+
* An expression that forces the output path to be in the current working folder.
126+
* Recognizes the pattern: `path.join(cwd, path.join('/', orgPath))`.
127+
*/
128+
class PathSanitizer extends Sanitizer, DataFlow::CallNode {
129+
PathSanitizer() {
130+
this = NodeJSLib::Path::moduleMember("join").getACall() and
131+
exists(DataFlow::CallNode inner | inner = getArgument(1) |
132+
inner = NodeJSLib::Path::moduleMember("join").getACall() and
133+
inner.getArgument(0).mayHaveStringValue("/")
134+
)
135+
}
136+
}
137+
122138
/**
123139
* Gets a string which is sufficient to exclude to make
124140
* a filepath definitely not refer to parent directories.

javascript/ql/test/query-tests/Security/CWE-022/ZipSlip/TarSlipBad.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ const extract = tar.extract();
44

55
extract.on('entry', (header, stream, next) => {
66
const out = fs.createWriteStream(header.name);
7+
8+
if (header.linkname) {
9+
fs.linkSync(header.linkname, "foo");
10+
}
11+
712
stream.pipe(out);
813
stream.on('end', () => {
914
next();

javascript/ql/test/query-tests/Security/CWE-022/ZipSlip/ZipSlip.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ nodes
55
| TarSlipBad.js:6:36:6:46 | header.name |
66
| TarSlipBad.js:6:36:6:46 | header.name |
77
| TarSlipBad.js:6:36:6:46 | header.name |
8+
| TarSlipBad.js:9:17:9:31 | header.linkname |
9+
| TarSlipBad.js:9:17:9:31 | header.linkname |
10+
| TarSlipBad.js:9:17:9:31 | header.linkname |
811
| ZipSlipBad2.js:5:9:5:46 | fileName |
912
| ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path |
1013
| ZipSlipBad2.js:5:37:5:46 | entry.path |
@@ -29,6 +32,7 @@ nodes
2932
edges
3033
| AdmZipBad.js:6:24:6:41 | zipEntry.entryName | AdmZipBad.js:6:24:6:41 | zipEntry.entryName |
3134
| TarSlipBad.js:6:36:6:46 | header.name | TarSlipBad.js:6:36:6:46 | header.name |
35+
| TarSlipBad.js:9:17:9:31 | header.linkname | TarSlipBad.js:9:17:9:31 | header.linkname |
3236
| ZipSlipBad2.js:5:9:5:46 | fileName | ZipSlipBad2.js:6:22:6:29 | fileName |
3337
| ZipSlipBad2.js:5:9:5:46 | fileName | ZipSlipBad2.js:6:22:6:29 | fileName |
3438
| ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path | ZipSlipBad2.js:5:9:5:46 | fileName |
@@ -49,6 +53,7 @@ edges
4953
#select
5054
| AdmZipBad.js:6:24:6:41 | zipEntry.entryName | AdmZipBad.js:6:24:6:41 | zipEntry.entryName | AdmZipBad.js:6:24:6:41 | zipEntry.entryName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | AdmZipBad.js:6:24:6:41 | zipEntry.entryName | item path |
5155
| TarSlipBad.js:6:36:6:46 | header.name | TarSlipBad.js:6:36:6:46 | header.name | TarSlipBad.js:6:36:6:46 | header.name | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | TarSlipBad.js:6:36:6:46 | header.name | item path |
56+
| TarSlipBad.js:9:17:9:31 | header.linkname | TarSlipBad.js:9:17:9:31 | header.linkname | TarSlipBad.js:9:17:9:31 | header.linkname | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | TarSlipBad.js:9:17:9:31 | header.linkname | item path |
5257
| ZipSlipBad2.js:6:22:6:29 | fileName | ZipSlipBad2.js:5:37:5:46 | entry.path | ZipSlipBad2.js:6:22:6:29 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad2.js:5:37:5:46 | entry.path | item path |
5358
| ZipSlipBad.js:8:37:8:44 | fileName | ZipSlipBad.js:7:22:7:31 | entry.path | ZipSlipBad.js:8:37:8:44 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad.js:7:22:7:31 | entry.path | item path |
5459
| ZipSlipBad.js:16:30:16:37 | fileName | ZipSlipBad.js:15:22:15:31 | entry.path | ZipSlipBad.js:16:30:16:37 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad.js:15:22:15:31 | entry.path | item path |

javascript/ql/test/query-tests/Security/CWE-022/ZipSlip/ZipSlipGood.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs');
22
const unzip = require('unzip');
3+
const path = require('path');
34

45
fs.createReadStream('archive.zip')
56
.pipe(unzip.Parse())
@@ -11,4 +12,6 @@ fs.createReadStream('archive.zip')
1112
else {
1213
console.log('skipping bad path', fileName);
1314
}
15+
16+
fs.createWriteStream(path.join(cwd, path.join('/', fileName)));
1417
});

javascript/ql/test/query-tests/Security/CWE-022/ZipSlip/externs.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,10 @@ var fs = {};
99
* @return {void}
1010
*/
1111
fs.writeFileSync = function(filename, data) {};
12+
13+
/**
14+
* @param {(string|Buffer)} srcpath
15+
* @param {(string|Buffer)} dstpath
16+
* @return {void}
17+
*/
18+
fs.linkSync = function(srcpath, dstpath) {};

0 commit comments

Comments
 (0)