Skip to content

Commit 8d41ce1

Browse files
authored
Merge pull request github#3480 from erik-krogh/moreSlip
Approved by esbena
2 parents a42d80a + 6d79bab commit 8d41ce1

File tree

7 files changed

+64
-27
lines changed

7 files changed

+64
-27
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
| 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. |
2828
| Unknown directive (`js/unknown-directive`) | Less results | This query no longer flags directives generated by the Babel compiler. |
2929
| Code injection (`js/code-injection`) | More results | More potential vulnerabilities involving NoSQL code operators are now recognized. |
30-
| Zip Slip (`js/zipslip`) | More results | This query now recognizes zip-slip vulnerabilities involving links. |
30+
| Zip Slip (`js/zipslip`) | More results | This query now recognizes additional vulnerabilities. |
3131

3232
## Changes to libraries
3333

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

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ module NodeJSLib {
306306

307307
FsFlowTarget() {
308308
exists(DataFlow::CallNode call, string methodName |
309-
call = DataFlow::moduleMember("fs", methodName).getACall()
309+
call = FS::moduleMember(methodName).getACall()
310310
|
311311
methodName = "realpathSync" and
312312
tainted = call.getArgument(0) and
@@ -430,27 +430,32 @@ module NodeJSLib {
430430
}
431431

432432
/**
433-
* A member `member` from module `fs` or its drop-in replacements `graceful-fs`, `fs-extra`, `original-fs`.
433+
* Provides predicates for working with the "fs" module and its variants as a single module.
434434
*/
435-
private DataFlow::SourceNode fsModuleMember(string member) {
436-
result = fsModule(DataFlow::TypeTracker::end()).getAPropertyRead(member)
437-
}
435+
module FS {
436+
/**
437+
* A member `member` from module `fs` or its drop-in replacements `graceful-fs`, `fs-extra`, `original-fs`.
438+
*/
439+
DataFlow::SourceNode moduleMember(string member) {
440+
result = fsModule(DataFlow::TypeTracker::end()).getAPropertyRead(member)
441+
}
438442

439-
private DataFlow::SourceNode fsModule(DataFlow::TypeTracker t) {
440-
exists(string moduleName |
441-
moduleName = "fs" or
442-
moduleName = "graceful-fs" or
443-
moduleName = "fs-extra" or
444-
moduleName = "original-fs"
445-
|
446-
result = DataFlow::moduleImport(moduleName)
443+
private DataFlow::SourceNode fsModule(DataFlow::TypeTracker t) {
444+
exists(string moduleName |
445+
moduleName = "fs" or
446+
moduleName = "graceful-fs" or
447+
moduleName = "fs-extra" or
448+
moduleName = "original-fs"
449+
|
450+
result = DataFlow::moduleImport(moduleName)
451+
or
452+
// extra support for flexible names
453+
result.asExpr().(Require).getArgument(0).mayHaveStringValue(moduleName)
454+
) and
455+
t.start()
447456
or
448-
// extra support for flexible names
449-
result.asExpr().(Require).getArgument(0).mayHaveStringValue(moduleName)
450-
) and
451-
t.start()
452-
or
453-
exists(DataFlow::TypeTracker t2 | result = fsModule(t2).track(t2, t))
457+
exists(DataFlow::TypeTracker t2 | result = fsModule(t2).track(t2, t))
458+
}
454459
}
455460

456461
/**
@@ -459,7 +464,7 @@ module NodeJSLib {
459464
private class NodeJSFileSystemAccess extends FileSystemAccess, DataFlow::CallNode {
460465
string methodName;
461466

462-
NodeJSFileSystemAccess() { this = maybePromisified(fsModuleMember(methodName)).getACall() }
467+
NodeJSFileSystemAccess() { this = maybePromisified(FS::moduleMember(methodName)).getACall() }
463468

464469
/**
465470
* Gets the name of the called method.
@@ -582,8 +587,8 @@ module NodeJSLib {
582587
name = "readdir" or
583588
name = "realpath"
584589
|
585-
this = fsModuleMember(name).getACall().getCallback([1 .. 2]).getParameter(1) or
586-
this = fsModuleMember(name + "Sync").getACall()
590+
this = FS::moduleMember(name).getACall().getCallback([1 .. 2]).getParameter(1) or
591+
this = FS::moduleMember(name + "Sync").getACall()
587592
)
588593
}
589594
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,11 @@ module TaintedPath {
155155
input = getAnArgument() and
156156
output = this
157157
or
158-
this = DataFlow::moduleMember("fs", "realpathSync").getACall() and
158+
this = NodeJSLib::FS::moduleMember("realpathSync").getACall() and
159159
input = getArgument(0) and
160160
output = this
161161
or
162-
this = DataFlow::moduleMember("fs", "realpath").getACall() and
162+
this = NodeJSLib::FS::moduleMember("realpath").getACall() and
163163
input = getArgument(0) and
164164
output = getCallback(1).getParameter(1)
165165
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,14 @@ module ZipSlip {
107107
// However, we want to consider even the bare `createWriteStream`
108108
// to be a zipslip vulnerability since it may truncate an
109109
// existing file.
110-
this = DataFlow::moduleImport("fs").getAMemberCall("createWriteStream").getArgument(0)
110+
this = NodeJSLib::FS::moduleMember("createWriteStream").getACall().getArgument(0)
111+
or
112+
// Not covered by `FileSystemWriteSink` because a later call
113+
// to `fs.write` is required for a write to take place.
114+
exists(DataFlow::CallNode call | this = call.getArgument(0) |
115+
call = NodeJSLib::FS::moduleMember(["open", "openSync"]).getACall() and
116+
call.getArgument(1).getStringValue().regexpMatch("(?i)w.{0,2}")
117+
)
111118
}
112119
}
113120

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ nodes
2424
| ZipSlipBad.js:15:22:15:31 | entry.path |
2525
| ZipSlipBad.js:16:30:16:37 | fileName |
2626
| ZipSlipBad.js:16:30:16:37 | fileName |
27+
| ZipSlipBad.js:22:11:22:31 | fileName |
28+
| ZipSlipBad.js:22:22:22:31 | entry.path |
29+
| ZipSlipBad.js:22:22:22:31 | entry.path |
30+
| ZipSlipBad.js:23:28:23:35 | fileName |
31+
| ZipSlipBad.js:23:28:23:35 | fileName |
2732
| ZipSlipBadUnzipper.js:7:9:7:29 | fileName |
2833
| ZipSlipBadUnzipper.js:7:20:7:29 | entry.path |
2934
| ZipSlipBadUnzipper.js:7:20:7:29 | entry.path |
@@ -46,6 +51,10 @@ edges
4651
| ZipSlipBad.js:15:11:15:31 | fileName | ZipSlipBad.js:16:30:16:37 | fileName |
4752
| ZipSlipBad.js:15:22:15:31 | entry.path | ZipSlipBad.js:15:11:15:31 | fileName |
4853
| ZipSlipBad.js:15:22:15:31 | entry.path | ZipSlipBad.js:15:11:15:31 | fileName |
54+
| ZipSlipBad.js:22:11:22:31 | fileName | ZipSlipBad.js:23:28:23:35 | fileName |
55+
| ZipSlipBad.js:22:11:22:31 | fileName | ZipSlipBad.js:23:28:23:35 | fileName |
56+
| ZipSlipBad.js:22:22:22:31 | entry.path | ZipSlipBad.js:22:11:22:31 | fileName |
57+
| ZipSlipBad.js:22:22:22:31 | entry.path | ZipSlipBad.js:22:11:22:31 | fileName |
4958
| ZipSlipBadUnzipper.js:7:9:7:29 | fileName | ZipSlipBadUnzipper.js:8:37:8:44 | fileName |
5059
| ZipSlipBadUnzipper.js:7:9:7:29 | fileName | ZipSlipBadUnzipper.js:8:37:8:44 | fileName |
5160
| ZipSlipBadUnzipper.js:7:20:7:29 | entry.path | ZipSlipBadUnzipper.js:7:9:7:29 | fileName |
@@ -57,4 +66,5 @@ edges
5766
| 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 |
5867
| 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 |
5968
| 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 |
69+
| ZipSlipBad.js:23:28:23:35 | fileName | ZipSlipBad.js:22:22:22:31 | entry.path | ZipSlipBad.js:23:28:23:35 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad.js:22:22:22:31 | entry.path | item path |
6070
| ZipSlipBadUnzipper.js:8:37:8:44 | fileName | ZipSlipBadUnzipper.js:7:20:7:29 | entry.path | ZipSlipBadUnzipper.js:8:37:8:44 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBadUnzipper.js:7:20:7:29 | entry.path | item path |

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@ fs.createReadStream('archive.zip')
1515
const fileName = entry.path;
1616
entry.pipe(Writer({path: fileName}));
1717
});
18+
19+
fs.createReadStream('archive.zip')
20+
.pipe(unzip.Parse())
21+
.on('entry', entry => {
22+
const fileName = entry.path;
23+
var file = fs.openSync(fileName, "w");
24+
});

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,12 @@ fs.writeFileSync = function(filename, data) {};
1515
* @param {(string|Buffer)} dstpath
1616
* @return {void}
1717
*/
18-
fs.linkSync = function(srcpath, dstpath) {};
18+
fs.linkSync = function(srcpath, dstpath) {};
19+
20+
/**
21+
* @param {(string|Buffer)} path
22+
* @param {(string|number)} flags
23+
* @param {number=} mode
24+
* @return {number}
25+
*/
26+
fs.openSync = function(path, flags, mode) {};

0 commit comments

Comments
 (0)