Skip to content

Commit eef8137

Browse files
committed
add Dice package, add global taint steps by SharedTaintStep, use getASuccessor
1 parent faaddd4 commit eef8137

File tree

3 files changed

+72
-77
lines changed

3 files changed

+72
-77
lines changed

javascript/ql/lib/semmle/javascript/frameworks/FormParsers.qll

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,18 @@ module BusBoy {
3838
}
3939

4040
/**
41-
* Holds if busboy file data as additional taint steps according to a Readable Stream type
42-
*
43-
* TODO: I don't know how it can be a global taint step!
41+
* A busboy file data step according to a Readable Stream type
4442
*/
45-
predicate busBoyReadableAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
46-
exists(API::Node busboyOnEvent |
47-
busboyOnEvent = API::moduleImport("busboy").getReturn().getMember("on")
48-
|
49-
busboyOnEvent.getParameter(0).asSink().mayHaveStringValue("file") and
50-
customStreamPipeAdditionalTaintStep(busboyOnEvent.getParameter(1).getParameter(1), pred, succ)
51-
)
43+
private class AdditionalTaintStep extends TaintTracking::SharedTaintStep {
44+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
45+
exists(API::Node busboyOnEvent |
46+
busboyOnEvent = API::moduleImport("busboy").getReturn().getMember("on")
47+
|
48+
busboyOnEvent.getParameter(0).asSink().mayHaveStringValue("file") and
49+
customStreamPipeAdditionalTaintStep(busboyOnEvent.getParameter(1).getParameter(1), pred,
50+
succ)
51+
)
52+
}
5253
}
5354
}
5455

@@ -89,11 +90,6 @@ module Formidable {
8990
}
9091
}
9192

92-
API::Node test() {
93-
result =
94-
API::moduleImport("multiparty").getMember("Form").getInstance().getMember("on").getASuccessor*()
95-
}
96-
9793
/**
9894
* A module for modeling [multiparty](https://www.npmjs.com/package/multiparty) package
9995
*/
@@ -116,7 +112,7 @@ module Multiparty {
116112
this = on.getParameter(1).getParameter([0, 1]).asSource()
117113
or
118114
on.getParameter(0).asSink().mayHaveStringValue("part") and
119-
this = readableStreamDataNode(on.getParameter(1).getParameter(0)).asSink()
115+
this = readableStreamDataNode(on.getParameter(1).getParameter(0)).asSource()
120116
)
121117
)
122118
)
@@ -126,18 +122,58 @@ module Multiparty {
126122
}
127123

128124
/**
129-
* Holds if multiparty part data as additional taint steps according to a Readable Stream type
130-
*
131-
* TODO: I don't know how it can be a global taint step!
125+
* A multiparty part data step according to a Readable Stream type
126+
*/
127+
private class AdditionalTaintStep extends TaintTracking::SharedTaintStep {
128+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
129+
exists(API::Node multipartyOnEvent |
130+
multipartyOnEvent =
131+
API::moduleImport("multiparty").getMember("Form").getInstance().getMember("on")
132+
|
133+
multipartyOnEvent.getParameter(0).asSink().mayHaveStringValue("part") and
134+
customStreamPipeAdditionalTaintStep(multipartyOnEvent.getParameter(1).getParameter(0), pred,
135+
succ)
136+
)
137+
}
138+
}
139+
}
140+
141+
/**
142+
* A module for modeling [dicer](https://www.npmjs.com/package/dicer) package
143+
*/
144+
module Dicer {
145+
/**
146+
* A source of remote flow from the `dicer` library.
132147
*/
133-
predicate multipartyReadableAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
134-
exists(API::Node multipartyOnEvent |
135-
multipartyOnEvent =
136-
API::moduleImport("multiparty").getMember("Form").getInstance().getMember("on")
137-
|
138-
multipartyOnEvent.getParameter(0).asSink().mayHaveStringValue("part") and
139-
customStreamPipeAdditionalTaintStep(multipartyOnEvent.getParameter(1).getParameter(0), pred,
140-
succ)
141-
)
148+
private class DicerRemoteFlow extends RemoteFlowSource {
149+
DicerRemoteFlow() {
150+
exists(API::Node dicer | dicer = API::moduleImport("dicer").getInstance() |
151+
exists(API::Node on | on = dicer.getMember("on") |
152+
on.getParameter(0).asSink().mayHaveStringValue("part") and
153+
this = readableStreamDataNode(on.getParameter(1).getParameter(0)).asSource()
154+
or
155+
exists(API::Node onPart | onPart = on.getParameter(1).getParameter(0).getMember("on") |
156+
onPart.getParameter(0).asSink().mayHaveStringValue("header") and
157+
this = onPart.getParameter(1).getParameter(0).asSource()
158+
)
159+
)
160+
)
161+
}
162+
163+
override string getSourceType() { result = "parsed user value from Dicer" }
164+
}
165+
166+
/**
167+
* A dicer part data step according to a Readable Stream type
168+
*/
169+
private class AdditionalTaintStep extends TaintTracking::SharedTaintStep {
170+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
171+
exists(API::Node onEvent |
172+
onEvent = API::moduleImport("dicer").getInstance().getMember("on")
173+
|
174+
onEvent.getParameter(0).asSink().mayHaveStringValue("part") and
175+
customStreamPipeAdditionalTaintStep(onEvent.getParameter(1).getParameter(0), pred, succ)
176+
)
177+
}
142178
}
143179
}

javascript/ql/lib/semmle/javascript/frameworks/ReadableStream.qll

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,6 @@ API::Node nodeJsStream() {
118118
* and returns all nodes responsible for a data read access
119119
*/
120120
API::Node readableStreamDataNode(API::Node stream) {
121-
result = stream.getMember("read").getReturn()
122-
or
123121
// 'data' event
124122
exists(API::CallNode onEvent | onEvent = stream.getMember("on").getACall() |
125123
result = onEvent.getParameter(1).getParameter(0) and
@@ -128,7 +126,10 @@ API::Node readableStreamDataNode(API::Node stream) {
128126
or
129127
// 'Readable' event
130128
exists(API::CallNode onEvent | onEvent = stream.getMember("on").getACall() |
131-
result = onEvent.getParameter(1).getReceiver().getMember("read").getReturn() and
129+
(
130+
result = onEvent.getParameter(1).getReceiver().getMember("read").getReturn() or
131+
result = stream.getMember("read").getReturn()
132+
) and
132133
onEvent.getParameter(0).asSink().mayHaveStringValue("readable")
133134
)
134135
}

javascript/ql/src/experimental/Security/CWE-522-DecompressionBombs/DecompressionBombs.ql

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -215,19 +215,7 @@ module DecompressionBomb {
215215
result =
216216
this.getMember(["extractAllTo", "extractEntryTo", "readAsText"]).getReturn().asSource()
217217
or
218-
result = this.getAAdmZipSuccessor().getMember("getData").getReturn().asSource()
219-
}
220-
221-
API::Node getAAdmZipSuccessor() {
222-
result = this
223-
or
224-
result = this.getAAdmZipSuccessor().getAMember()
225-
or
226-
result = this.getAAdmZipSuccessor().getAParameter()
227-
or
228-
result = this.getAAdmZipSuccessor().getReturn()
229-
or
230-
result = this.getAAdmZipSuccessor().getPromised()
218+
result = this.getASuccessor*().getMember("getData").getReturn().asSource()
231219
}
232220
}
233221

@@ -244,7 +232,6 @@ module DecompressionBomb {
244232
.getReturn()
245233
.asSource()
246234
or
247-
// I can't find an alternative for getASuccessor*() for here
248235
succ =
249236
n.getInstance()
250237
.getMember("getEntries")
@@ -340,21 +327,7 @@ module DecompressionBomb {
340327
* so i'm going to check if there is a member like `vars.uncompressedSize` in whole DB or not!
341328
*/
342329
predicate sanitizer() {
343-
exists(this.getAGzipperSuccessor().getMember("vars").getMember("uncompressedSize"))
344-
}
345-
346-
API::Node getAGzipperSuccessor() {
347-
(
348-
result = API::moduleImport("stream")
349-
or
350-
result = this.getAGzipperSuccessor().getAMember()
351-
or
352-
result = this.getAGzipperSuccessor().getAParameter()
353-
or
354-
result = this.getAGzipperSuccessor().getReturn()
355-
or
356-
result = this.getAGzipperSuccessor().getPromised()
357-
) and
330+
exists(this.getASuccessor*().getMember("vars").getMember("uncompressedSize")) and
358331
funcName = ["Extract", "Parse", "ParseOne"]
359332
}
360333
}
@@ -381,35 +354,20 @@ module DecompressionBomb {
381354
}
382355

383356
override DataFlow::Node sink() {
384-
result = this.getAYauzlSuccessor().getMember("readEntry").getACall() and
357+
result = this.getASuccessor*().getMember("readEntry").getACall() and
385358
not this.sanitizer() and
386359
isOpenFunc = true
387360
or
388361
result = this.getACall().getArgument(0) and
389362
isOpenFunc = false
390363
}
391364

392-
API::Node getAYauzlSuccessor() {
393-
(
394-
result = this
395-
or
396-
result = this.getAYauzlSuccessor().getAMember()
397-
or
398-
result = this.getAYauzlSuccessor().getAParameter()
399-
or
400-
result = this.getAYauzlSuccessor().getReturn()
401-
or
402-
result = this.getAYauzlSuccessor().getPromised()
403-
) and
404-
isOpenFunc = true
405-
}
406-
407365
/**
408366
* Gets a
409367
* and Holds if yauzl `open` instance has a member `uncompressedSize`
410368
*/
411369
predicate sanitizer() {
412-
exists(this.getAYauzlSuccessor().getMember("uncompressedSize")) and
370+
exists(this.getASuccessor*().getMember("uncompressedSize")) and
413371
isOpenFunc = true
414372
}
415373
}

0 commit comments

Comments
 (0)