Skip to content

Commit 016e6d2

Browse files
authored
Merge pull request #4275 from erik-krogh/CVE760-indirect
Approved by esbena
2 parents d867172 + 9e7a193 commit 016e6d2

File tree

15 files changed

+361
-26
lines changed

15 files changed

+361
-26
lines changed

change-notes/1.26/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
* Support for the following frameworks and libraries has been improved:
66
- [bluebird](https://www.npmjs.com/package/bluebird)
7+
- [express](https://www.npmjs.com/package/express)
78
- [fast-json-stable-stringify](https://www.npmjs.com/package/fast-json-stable-stringify)
89
- [fast-safe-stringify](https://www.npmjs.com/package/fast-safe-stringify)
10+
- [http](https://nodejs.org/api/http.html)
911
- [javascript-stringify](https://www.npmjs.com/package/javascript-stringify)
1012
- [js-stringify](https://www.npmjs.com/package/js-stringify)
1113
- [json-stable-stringify](https://www.npmjs.com/package/json-stable-stringify)

javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ private DataFlow::SourceNode getARouteUsingCookies(DataFlow::TypeTracker t) {
4646
t.start() and
4747
isRouteHandlerUsingCookies(result)
4848
or
49-
exists(DataFlow::TypeTracker t2 | result = getARouteUsingCookies(t2).track(t2, t))
49+
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = getARouteUsingCookies(t2) |
50+
result = pred.track(t2, t)
51+
or
52+
t = t2 and
53+
HTTP::routeHandlerStep(pred, result)
54+
)
5055
}
5156

5257
/** Gets a data flow node referring to a route handler that uses cookies. */

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,7 @@ module Express {
125125
exists(DataFlow::TypeBackTracker t2, DataFlow::SourceNode succ | succ = getARouteHandler(t2) |
126126
result = succ.backtrack(t2, t)
127127
or
128-
exists(HTTP::RouteHandlerCandidateContainer container |
129-
result = container.getRouteHandler(succ)
130-
) and
128+
HTTP::routeHandlerStep(result, succ) and
131129
t = t2
132130
)
133131
}

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

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,53 @@ module HTTP {
233233
ResponseExpr getAResponseExpr() { result.getRouteHandler() = this }
234234
}
235235

236+
/**
237+
* Holds if `call` decorates the function `pred`.
238+
* This means that `call` returns a function that forwards its arguments to `pred`.
239+
* Only holds when the decorator looks like it is decorating a route-handler.
240+
*/
241+
private predicate isDecoratedCall(DataFlow::CallNode call, DataFlow::FunctionNode decoratee) {
242+
// indirect route-handler `result` is given to function `outer`, which returns function `inner` which calls the function `pred`.
243+
exists(int i, Function outer, Function inner |
244+
decoratee = call.getArgument(i).getALocalSource() and
245+
outer = call.getACallee() and
246+
inner = outer.getAReturnedExpr() and
247+
isAForwardingRouteHandlerCall(DataFlow::parameterNode(outer.getParameter(i)), inner.flow())
248+
)
249+
}
250+
251+
/**
252+
* Holds if `f` looks like a route-handler and a call to `callee` inside `f` forwards all of the parameters from `f` to that call.
253+
*/
254+
private predicate isAForwardingRouteHandlerCall(
255+
DataFlow::SourceNode callee, HTTP::RouteHandlerCandidate f
256+
) {
257+
exists(DataFlow::CallNode call | call = callee.getACall() |
258+
forall(int arg | arg = [0 .. f.getNumParameter() - 1] |
259+
f.getParameter(arg).flowsTo(call.getArgument(arg))
260+
) and
261+
call.getContainer() = f.getFunction()
262+
)
263+
}
264+
265+
/**
266+
* Holds if there exists a step from `pred` to `succ` for a RouteHandler - beyond the usual steps defined by TypeTracking.
267+
*/
268+
predicate routeHandlerStep(DataFlow::SourceNode pred, DataFlow::SourceNode succ) {
269+
isDecoratedCall(succ, pred)
270+
or
271+
// A forwarding call
272+
isAForwardingRouteHandlerCall(pred, succ)
273+
or
274+
// a container containing route-handlers.
275+
exists(HTTP::RouteHandlerCandidateContainer container | pred = container.getRouteHandler(succ))
276+
or
277+
// (function (req, res) {}).bind(this);
278+
exists(DataFlow::PartialInvokeNode call |
279+
succ = call.getBoundFunction(any(DataFlow::Node n | pred.flowsTo(n)), 0)
280+
)
281+
}
282+
236283
/**
237284
* An expression that sets up a route on a server.
238285
*/
@@ -555,26 +602,51 @@ module HTTP {
555602
create.getArgument(0).asExpr() instanceof NullLiteral
556603
)
557604
) and
558-
exists(RouteHandlerCandidate candidate | candidate.flowsTo(getAPropertyWrite().getRhs()))
605+
exists(RouteHandlerCandidate candidate |
606+
getAPossiblyDecoratedHandler(candidate).flowsTo(getAPropertyWrite().getRhs())
607+
)
559608
}
560609

561610
override DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
562611
result instanceof RouteHandlerCandidate and
563612
exists(DataFlow::PropWrite write, DataFlow::PropRead read |
564613
access = read and
565614
ref(this).getAPropertyRead() = read and
566-
result.flowsTo(write.getRhs()) and
615+
getAPossiblyDecoratedHandler(result).flowsTo(write.getRhs()) and
567616
write = this.getAPropertyWrite()
568617
|
569618
write.getPropertyName() = read.getPropertyName()
570619
or
571620
exists(EnumeratedPropName prop | access = prop.getASourceProp())
572621
or
573622
read = DataFlow::lvalueNode(any(ForOfStmt stmt).getLValue())
623+
or
624+
// for forwarding calls to an element where the key is determined by the request.
625+
getRequestParameterRead().flowsToExpr(read.getPropertyNameExpr())
574626
)
575627
}
576628
}
577629

630+
/**
631+
* Gets a (chained) property-read/method-call on the request parameter of the route-handler `f`.
632+
*/
633+
private DataFlow::SourceNode getRequestParameterRead() {
634+
result = any(RouteHandlerCandidate f).getParameter(0)
635+
or
636+
result = getRequestParameterRead().getAPropertyRead()
637+
or
638+
result = getRequestParameterRead().getAMethodCall()
639+
}
640+
641+
/**
642+
* Gets a node that is either `candidate`, or a call that decorates `candidate`.
643+
*/
644+
DataFlow::SourceNode getAPossiblyDecoratedHandler(RouteHandlerCandidate candidate) {
645+
result = candidate
646+
or
647+
isDecoratedCall(result, candidate)
648+
}
649+
578650
/**
579651
* A collection that contains one or more route potential handlers.
580652
*/
@@ -592,13 +664,14 @@ module HTTP {
592664
}
593665

594666
override DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
667+
result instanceof RouteHandlerCandidate and
595668
exists(
596669
DataFlow::Node input, TypeTrackingPseudoProperty key, CollectionFlowStep store,
597670
CollectionFlowStep load, DataFlow::Node storeTo, DataFlow::Node loadFrom
598671
|
599672
this.flowsTo(storeTo) and
600673
store.store(input, storeTo, key) and
601-
result.(RouteHandlerCandidate).flowsTo(input) and
674+
getAPossiblyDecoratedHandler(result).flowsTo(input) and
602675
ref(this).flowsTo(loadFrom) and
603676
load.load(loadFrom, access, key)
604677
)

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,12 @@ module NodeJSLib {
228228
t.start() and
229229
result = handler.flow().getALocalSource()
230230
or
231-
exists(DataFlow::TypeBackTracker t2 | result = getARouteHandler(t2).backtrack(t2, t))
231+
exists(DataFlow::TypeBackTracker t2, DataFlow::SourceNode succ | succ = getARouteHandler(t2) |
232+
result = succ.backtrack(t2, t)
233+
or
234+
t = t2 and
235+
HTTP::routeHandlerStep(result, succ)
236+
)
232237
}
233238

234239
override Expr getServer() { result = server }
@@ -721,16 +726,8 @@ module NodeJSLib {
721726
astNode.getParameter(0).getName() = request and
722727
astNode.getParameter(1).getName() = response
723728
|
724-
not (
725-
// heuristic: not a class method (Node.js invokes this with a function call)
726-
astNode = any(MethodDefinition def).getBody()
727-
or
728-
// heuristic: does not return anything (Node.js will not use the return value)
729-
exists(astNode.getAReturnStmt().getExpr())
730-
or
731-
// heuristic: is not invoked (Node.js invokes this at a call site we cannot reason precisely about)
732-
exists(DataFlow::InvokeNode cs | cs.getACallee() = astNode)
733-
)
729+
// heuristic: not a class method (Node.js invokes this with a function call)
730+
not astNode = any(MethodDefinition def).getBody()
734731
)
735732
}
736733
}

0 commit comments

Comments
 (0)