-
Notifications
You must be signed in to change notification settings - Fork 2.1k
JavaScript: Add tar-stream extraction to ZipSlip query. #1118
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
Changes from all commits
6589813
a674dbb
8124980
aa9ba95
4475dd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,31 +1,32 @@ | ||
| /** | ||
| * Provides a taint tracking configuration for reasoning about unsafe zip extraction. | ||
| * Provides a taint tracking configuration for reasoning about unsafe | ||
| * zip and tar archive extraction. | ||
| */ | ||
|
|
||
| import javascript | ||
|
|
||
| module ZipSlip { | ||
| /** | ||
| * A data flow source for unsafe zip extraction. | ||
| * A data flow source for unsafe archive extraction. | ||
| */ | ||
| abstract class Source extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A data flow sink for unsafe zip extraction. | ||
| * A data flow sink for unsafe archive extraction. | ||
| */ | ||
| abstract class Sink extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A sanitizer for unsafe zip extraction. | ||
| * A sanitizer for unsafe archive extraction. | ||
| */ | ||
| abstract class Sanitizer extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A sanitizer guard for unsafe zip extraction. | ||
| * A sanitizer guard for unsafe archive extraction. | ||
| */ | ||
| abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { } | ||
|
|
||
| /** A taint tracking configuration for unsafe zip extraction. */ | ||
| /** A taint tracking configuration for unsafe archive extraction. */ | ||
| class Configuration extends TaintTracking::Configuration { | ||
| Configuration() { this = "ZipSlip" } | ||
|
|
||
|
|
@@ -41,11 +42,15 @@ module ZipSlip { | |
| } | ||
|
|
||
| /** | ||
| * Gets a node that can be a parsed zip archive. | ||
| * Gets a node that can be a parsed archive. | ||
| */ | ||
| private DataFlow::SourceNode parsedArchive() { | ||
| result = DataFlow::moduleImport("unzipper").getAMemberCall("Parse") | ||
| or | ||
| result = DataFlow::moduleImport("unzip").getAMemberCall("Parse") | ||
| or | ||
| result = DataFlow::moduleImport("tar-stream").getAMemberCall("extract") | ||
| or | ||
| // `streamProducer.pipe(unzip.Parse())` is a typical (but not | ||
| // universal) pattern when using nodejs streams, whose return | ||
| // value is the parsed stream. | ||
|
|
@@ -56,7 +61,14 @@ module ZipSlip { | |
| ) | ||
| } | ||
|
|
||
| /** A zip archive entry path access, as a source for unsafe zip extraction. */ | ||
| /** Gets a property that is used to get the filename part of an archive entry. */ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume it's "path" for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, done. |
||
| private string getAFilenameProperty() { | ||
| result = "path" // Used by library 'unzip'. | ||
| or | ||
| result = "name" // Used by library 'tar-stream'. | ||
| } | ||
|
|
||
| /** An archive entry path access, as a source for unsafe archive extraction. */ | ||
| class UnzipEntrySource extends Source { | ||
| // For example, in | ||
| // ```javascript | ||
|
|
@@ -74,13 +86,12 @@ module ZipSlip { | |
| exists(DataFlow::CallNode cn | | ||
| cn = parsedArchive().getAMemberCall("on") and | ||
| cn.getArgument(0).mayHaveStringValue("entry") and | ||
| this = cn.getCallback(1) | ||
| .getParameter(0) | ||
| .getAPropertyRead("path")) | ||
| this = cn.getCallback(1).getParameter(0).getAPropertyRead(getAFilenameProperty()) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /** A call to `fs.createWriteStream`, as a sink for unsafe zip extraction. */ | ||
| /** A call to `fs.createWriteStream`, as a sink for unsafe archive extraction. */ | ||
| class CreateWriteStreamSink extends Sink { | ||
| CreateWriteStreamSink() { | ||
| // This is not covered by `FileSystemWriteSink`, because it is | ||
|
|
@@ -92,16 +103,14 @@ module ZipSlip { | |
| } | ||
| } | ||
|
|
||
| /** A file path of a file write, as a sink for unsafe zip extraction. */ | ||
| /** A file path of a file write, as a sink for unsafe archive extraction. */ | ||
| class FileSystemWriteSink extends Sink { | ||
| FileSystemWriteSink() { exists(FileSystemWriteAccess fsw | fsw.getAPathArgument() = this) } | ||
| } | ||
|
|
||
| /** An expression that sanitizes by calling path.basename */ | ||
| class BasenameSanitizer extends Sanitizer { | ||
| BasenameSanitizer() { | ||
| this = DataFlow::moduleImport("path").getAMemberCall("basename") | ||
| } | ||
| BasenameSanitizer() { this = DataFlow::moduleImport("path").getAMemberCall("basename") } | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| const fs = require('fs'); | ||
| const tar = require('tar-stream'); | ||
| const extract = tar.extract(); | ||
|
|
||
| extract.on('entry', (header, stream, next) => { | ||
| const out = fs.createWriteStream(header.name); | ||
| stream.pipe(out); | ||
| stream.on('end', () => { | ||
| next(); | ||
| }) | ||
| stream.resume(); | ||
| }) | ||
|
|
||
| extract.on('finish', () => { | ||
| console.log('finished'); | ||
| }); | ||
|
|
||
| fs.createReadStream('./bad.tar').pipe(extract); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,25 @@ | ||
| nodes | ||
| | TarSlipBad.js:6:36:6:46 | header.name | | ||
| | ZipSlipBad2.js:5:9:5:46 | fileName | | ||
| | ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path | | ||
| | ZipSlipBad2.js:5:37:5:46 | entry.path | | ||
| | ZipSlipBad2.js:6:22:6:29 | fileName | | ||
| | ZipSlipBad.js:7:11:7:31 | fileName | | ||
| | ZipSlipBad.js:7:22:7:31 | entry.path | | ||
| | ZipSlipBad.js:8:37:8:44 | fileName | | ||
| | ZipSlipBadUnzipper.js:7:9:7:29 | fileName | | ||
| | ZipSlipBadUnzipper.js:7:20:7:29 | entry.path | | ||
| | ZipSlipBadUnzipper.js:8:37:8:44 | fileName | | ||
| edges | ||
| | ZipSlipBad2.js:5:9:5:46 | fileName | ZipSlipBad2.js:6:22:6:29 | fileName | | ||
| | ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path | ZipSlipBad2.js:5:9:5:46 | fileName | | ||
| | ZipSlipBad2.js:5:37:5:46 | entry.path | ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path | | ||
| | ZipSlipBad.js:7:11:7:31 | fileName | ZipSlipBad.js:8:37:8:44 | fileName | | ||
| | ZipSlipBad.js:7:22:7:31 | entry.path | ZipSlipBad.js:7:11:7:31 | fileName | | ||
| | ZipSlipBadUnzipper.js:7:9:7:29 | fileName | ZipSlipBadUnzipper.js:8:37:8:44 | fileName | | ||
| | ZipSlipBadUnzipper.js:7:20:7:29 | entry.path | ZipSlipBadUnzipper.js:7:9:7:29 | fileName | | ||
| #select | ||
| | 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 | | ||
| | 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 | | ||
| | 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 | | ||
| | 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 | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| const fs = require('fs'); | ||
| const unzipper = require('unzipper'); | ||
|
|
||
| fs.createReadStream('path/to/archive.zip') | ||
| .pipe(unzipper.Parse()) | ||
| .on('entry', function (entry) { | ||
| var fileName = entry.path; | ||
| entry.pipe(fs.createWriteStream(fileName)); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a test case for this one as well?