11/**
22 * Provides a taint tracking configuration for reasoning about unsafe
33 * zip and tar archive extraction.
4+ *
5+ * Note, for performance reasons: only import this file if
6+ * `ZipSlip::Configuration` is needed, otherwise
7+ * `ZipSlipCustomizations` should be imported instead.
48 */
59
610import javascript
711
812module ZipSlip {
9- /**
10- * A data flow source for unsafe archive extraction.
11- */
12- abstract class Source extends DataFlow:: Node { }
13-
14- /**
15- * A data flow sink for unsafe archive extraction.
16- */
17- abstract class Sink extends DataFlow:: Node { }
18-
19- /**
20- * A sanitizer for unsafe archive extraction.
21- */
22- abstract class Sanitizer extends DataFlow:: Node { }
23-
24- /**
25- * A sanitizer guard for unsafe archive extraction.
26- */
27- abstract class SanitizerGuard extends TaintTracking:: SanitizerGuardNode , DataFlow:: ValueNode { }
13+ import ZipSlipCustomizations:: ZipSlip
2814
2915 /** A taint tracking configuration for unsafe archive extraction. */
3016 class Configuration extends TaintTracking:: Configuration {
@@ -40,115 +26,4 @@ module ZipSlip {
4026 nd instanceof SanitizerGuard
4127 }
4228 }
43-
44- /**
45- * Gets a node that can be a parsed archive.
46- */
47- private DataFlow:: SourceNode parsedArchive ( ) {
48- result = DataFlow:: moduleImport ( "unzipper" ) .getAMemberCall ( "Parse" )
49- or
50- result = DataFlow:: moduleImport ( "unzip" ) .getAMemberCall ( "Parse" )
51- or
52- result = DataFlow:: moduleImport ( "tar-stream" ) .getAMemberCall ( "extract" )
53- or
54- // `streamProducer.pipe(unzip.Parse())` is a typical (but not
55- // universal) pattern when using nodejs streams, whose return
56- // value is the parsed stream.
57- exists ( DataFlow:: MethodCallNode pipe |
58- pipe = result and
59- pipe .getMethodName ( ) = "pipe" and
60- parsedArchive ( ) .flowsTo ( pipe .getArgument ( 0 ) )
61- )
62- }
63-
64- /** Gets a property that is used to get the filename part of an archive entry. */
65- private string getAFilenameProperty ( ) {
66- result = "path" // Used by library 'unzip'.
67- or
68- result = "name" // Used by library 'tar-stream'.
69- }
70-
71- /** An archive entry path access, as a source for unsafe archive extraction. */
72- class UnzipEntrySource extends Source {
73- // For example, in
74- // ```javascript
75- // const unzip = require('unzip');
76- //
77- // fs.createReadStream('archive.zip')
78- // .pipe(unzip.Parse())
79- // .on('entry', entry => {
80- // const path = entry.path;
81- // });
82- // ```
83- // there is an `UnzipEntrySource` node corresponding to
84- // the expression `entry.path`.
85- UnzipEntrySource ( ) {
86- exists ( DataFlow:: CallNode cn |
87- cn = parsedArchive ( ) .getAMemberCall ( "on" ) and
88- cn .getArgument ( 0 ) .mayHaveStringValue ( "entry" ) and
89- this = cn .getCallback ( 1 ) .getParameter ( 0 ) .getAPropertyRead ( getAFilenameProperty ( ) )
90- )
91- }
92- }
93-
94- /** An archive entry path access using the `adm-zip` package. */
95- class AdmZipEntrySource extends Source {
96- AdmZipEntrySource ( ) {
97- exists ( DataFlow:: SourceNode admZip , DataFlow:: SourceNode entry |
98- admZip = DataFlow:: moduleImport ( "adm-zip" ) .getAnInstantiation ( ) and
99- this = entry .getAPropertyRead ( "entryName" )
100- |
101- entry = admZip .getAMethodCall ( "getEntry" )
102- or
103- exists ( DataFlow:: SourceNode entries | entries = admZip .getAMethodCall ( "getEntries" ) |
104- entry = entries .getAPropertyRead ( )
105- or
106- exists ( string map | map = "map" or map = "forEach" |
107- entry = entries .getAMethodCall ( map ) .getCallback ( 0 ) .getParameter ( 0 )
108- )
109- )
110- )
111- }
112- }
113-
114- /** A call to `fs.createWriteStream`, as a sink for unsafe archive extraction. */
115- class CreateWriteStreamSink extends Sink {
116- CreateWriteStreamSink ( ) {
117- // This is not covered by `FileSystemWriteSink`, because it is
118- // required that a write actually takes place to the stream.
119- // However, we want to consider even the bare `createWriteStream`
120- // to be a zipslip vulnerability since it may truncate an
121- // existing file.
122- this = DataFlow:: moduleImport ( "fs" ) .getAMemberCall ( "createWriteStream" ) .getArgument ( 0 )
123- }
124- }
125-
126- /** A file path of a file write, as a sink for unsafe archive extraction. */
127- class FileSystemWriteSink extends Sink {
128- FileSystemWriteSink ( ) { exists ( FileSystemWriteAccess fsw | fsw .getAPathArgument ( ) = this ) }
129- }
130-
131- /** An expression that sanitizes by calling path.basename */
132- class BasenameSanitizer extends Sanitizer {
133- BasenameSanitizer ( ) { this = DataFlow:: moduleImport ( "path" ) .getAMemberCall ( "basename" ) }
134- }
135-
136- /**
137- * Gets a string which is sufficient to exclude to make
138- * a filepath definitely not refer to parent directories.
139- */
140- private string getAParentDirName ( ) { result = ".." or result = "../" }
141-
142- /** A check that a path string does not include '..' */
143- class NoParentDirSanitizerGuard extends SanitizerGuard {
144- StringOps:: Includes incl ;
145-
146- NoParentDirSanitizerGuard ( ) { this = incl }
147-
148- override predicate sanitizes ( boolean outcome , Expr e ) {
149- incl .getPolarity ( ) .booleanNot ( ) = outcome and
150- incl .getBaseString ( ) .asExpr ( ) = e and
151- incl .getSubstring ( ) .mayHaveStringValue ( getAParentDirName ( ) )
152- }
153- }
15429}
0 commit comments