Skip to content

Commit e4c8653

Browse files
committed
JS: Factor RequestHeaderAccess into separate class
1 parent c879654 commit e4c8653

File tree

10 files changed

+189
-96
lines changed

10 files changed

+189
-96
lines changed

javascript/ql/src/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class TaintedHostHeader extends TaintTracking::Configuration {
1414
TaintedHostHeader() { this = "TaintedHostHeader" }
1515

1616
override predicate isSource(DataFlow::Node node) {
17-
exists (HTTP::RequestInputAccess input | node = input |
17+
exists (HTTP::RequestHeaderAccess input | node = input |
1818
input.getKind() = "header" and
1919
input.getAHeaderName() = "host")
2020
}

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

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -471,41 +471,52 @@ module Express {
471471
propName = "originalUrl"
472472
)
473473
or
474+
// `req.cookies`
475+
kind = "cookie" and
476+
this.(DataFlow::PropRef).accesses(request, "cookies")
477+
)
478+
or
479+
exists (RequestHeaderAccess access | this = access |
480+
rh = access.getRouteHandler() and
481+
kind = "header")
482+
}
483+
484+
override RouteHandler getRouteHandler() {
485+
result = rh
486+
}
487+
488+
override string getKind() {
489+
result = kind
490+
}
491+
}
492+
493+
/**
494+
* An access to a header on an Express request.
495+
*/
496+
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
497+
RouteHandler rh;
498+
499+
RequestHeaderAccess() {
500+
exists (DataFlow::Node request | request = DataFlow::valueNode(rh.getARequestExpr()) |
474501
exists (string methodName |
475502
// `req.get(...)` or `req.header(...)`
476-
kind = "header" and
477503
this.(DataFlow::MethodCallNode).calls(request, methodName) |
478504
methodName = "get" or
479505
methodName = "header"
480506
)
481507
or
482508
exists (DataFlow::PropRead headers |
483509
// `req.headers.name`
484-
kind = "header" and
485510
headers.accesses(request, "headers") and
486511
this = headers.getAPropertyRead())
487512
or
488513
exists (string propName | propName = "host" or propName = "hostname" |
489514
// `req.host` and `req.hostname` are derived from headers
490-
kind = "header" and
491515
this.(DataFlow::PropRead).accesses(request, propName))
492-
or
493-
// `req.cookies`
494-
kind = "cookie" and
495-
this.(DataFlow::PropRef).accesses(request, "cookies")
496516
)
497517
}
498518

499-
override RouteHandler getRouteHandler() {
500-
result = rh
501-
}
502-
503-
override string getKind() {
504-
result = kind
505-
}
506-
507519
override string getAHeaderName() {
508-
kind = "header" and
509520
exists (string name |
510521
name = this.(DataFlow::PropRead).getPropertyName()
511522
or
@@ -516,6 +527,14 @@ module Express {
516527
else
517528
result = name.toLowerCase())
518529
}
530+
531+
override RouteHandler getRouteHandler() {
532+
result = rh
533+
}
534+
535+
override string getKind() {
536+
result = "header"
537+
}
519538
}
520539

521540
/**
@@ -613,9 +632,9 @@ module Express {
613632
astNode.getMethodName() = any(string n | n = "set" or n = "header") and
614633
astNode.getNumArgument() = 1
615634
}
616-
635+
617636
/**
618-
* Gets a reference to the multiple headers object that is to be set.
637+
* Gets a reference to the multiple headers object that is to be set.
619638
*/
620639
private DataFlow::SourceNode getAHeaderSource() {
621640
result.flowsToExpr(astNode.getArgument(0))
@@ -631,12 +650,12 @@ module Express {
631650
override RouteHandler getRouteHandler() {
632651
result = rh
633652
}
634-
653+
635654
override Expr getNameExpr() {
636-
exists (DataFlow::PropWrite write |
655+
exists (DataFlow::PropWrite write |
637656
getAHeaderSource().flowsTo(write.getBase()) and
638657
result = write.getPropertyNameExpr()
639-
)
658+
)
640659
}
641660
}
642661

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ module HTTP {
7272
* Holds if the header with (lower-case) name `headerName` is set to the value of `headerValue`.
7373
*/
7474
abstract predicate definesExplicitly(string headerName, Expr headerValue);
75-
75+
7676
/**
77-
* Returns the expression used to compute the header name.
77+
* Returns the expression used to compute the header name.
7878
*/
7979
abstract Expr getNameExpr();
8080
}
@@ -354,9 +354,9 @@ module HTTP {
354354
headerName = getNameExpr().getStringValue().toLowerCase() and
355355
headerValue = astNode.getArgument(1)
356356
}
357-
357+
358358
override Expr getNameExpr() {
359-
result = astNode.getArgument(0)
359+
result = astNode.getArgument(0)
360360
}
361361

362362
}
@@ -399,15 +399,19 @@ module HTTP {
399399
* Note that this predicate is functional.
400400
*/
401401
abstract string getKind();
402+
}
402403

404+
/**
405+
* An access to a header on an incoming HTTP request.
406+
*/
407+
abstract class RequestHeaderAccess extends RequestInputAccess {
403408
/**
404409
* Gets the lower-case name of an HTTP header from which this input is derived,
405410
* if this can be determined.
406411
*
407-
* When the input is not derived from a header, or the header name is
408-
* unknown, this has no result.
412+
* When the name of the header is unknown, this has no result.
409413
*/
410-
string getAHeaderName() { none() }
414+
abstract string getAHeaderName();
411415
}
412416

413417
/**

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,17 @@ module Hapi {
121121
this.asExpr().(PropAccess).accesses(url, "path")
122122
)
123123
or
124-
exists (PropAccess headers |
125-
// `request.headers.<name>`
126-
kind = "header" and
127-
headers.accesses(request, "headers") and
128-
this.asExpr().(PropAccess).accesses(headers, _)
129-
)
130-
or
131124
exists (PropAccess state |
132125
// `request.state.<name>`
133126
kind = "cookie" and
134127
state.accesses(request, "state") and
135128
this.asExpr().(PropAccess).accesses(state, _)
136129
)
137130
)
131+
or
132+
exists (RequestHeaderAccess access | this = access |
133+
rh = access.getRouteHandler() and
134+
kind = "header")
138135
}
139136

140137
override RouteHandler getRouteHandler() {
@@ -144,11 +141,35 @@ module Hapi {
144141
override string getKind() {
145142
result = kind
146143
}
144+
}
145+
146+
/**
147+
* An access to an HTTP header on a Hapi request.
148+
*/
149+
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
150+
RouteHandler rh;
151+
152+
RequestHeaderAccess() {
153+
exists (Expr request | request = rh.getARequestExpr() |
154+
exists (PropAccess headers |
155+
// `request.headers.<name>`
156+
headers.accesses(request, "headers") and
157+
this.asExpr().(PropAccess).accesses(headers, _)
158+
)
159+
)
160+
}
147161

148162
override string getAHeaderName() {
149-
kind = "header" and
150163
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
151164
}
165+
166+
override RouteHandler getRouteHandler() {
167+
result = rh
168+
}
169+
170+
override string getKind() {
171+
result = "header"
172+
}
152173
}
153174

154175
/**

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

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,6 @@ module Koa {
182182
propName = "originalUrl" or
183183
propName = "href"
184184
)
185-
or
186-
exists (string propName, PropAccess headers |
187-
// `ctx.request.header.<name>`, `ctx.request.headers.<name>`
188-
kind = "header" and
189-
headers.accesses(request, propName) and
190-
this.asExpr().(PropAccess).accesses(headers, _) |
191-
propName = "header" or
192-
propName = "headers"
193-
)
194-
or
195-
// `ctx.request.get(<name>)`
196-
kind = "header" and
197-
this.asExpr().(MethodCallExpr).calls(request, "get")
198185
)
199186
or
200187
exists (PropAccess cookies |
@@ -203,6 +190,10 @@ module Koa {
203190
cookies.accesses(rh.getAContextExpr(), "cookies") and
204191
this.asExpr().(MethodCallExpr).calls(cookies, "get")
205192
)
193+
or
194+
exists (RequestHeaderAccess access | access = this |
195+
rh = access.getRouteHandler() and
196+
kind = "header")
206197
}
207198

208199
override RouteHandler getRouteHandler() {
@@ -212,17 +203,44 @@ module Koa {
212203
override string getKind() {
213204
result = kind
214205
}
206+
}
215207

216-
override string getAHeaderName() {
217-
kind = "header" and
218-
(
219-
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
208+
/**
209+
* An access to an HTTP header on a Koa request.
210+
*/
211+
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
212+
RouteHandler rh;
213+
214+
RequestHeaderAccess() {
215+
exists (Expr request | request = rh.getARequestExpr() |
216+
exists (string propName, PropAccess headers |
217+
// `ctx.request.header.<name>`, `ctx.request.headers.<name>`
218+
headers.accesses(request, propName) and
219+
this.asExpr().(PropAccess).accesses(headers, _) |
220+
propName = "header" or
221+
propName = "headers"
222+
)
220223
or
221-
exists (string name |
222-
this.(DataFlow::CallNode).getArgument(0).mayHaveStringValue(name) and
223-
result = name.toLowerCase())
224+
// `ctx.request.get(<name>)`
225+
this.asExpr().(MethodCallExpr).calls(request, "get")
224226
)
225227
}
228+
229+
override string getAHeaderName() {
230+
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
231+
or
232+
exists (string name |
233+
this.(DataFlow::CallNode).getArgument(0).mayHaveStringValue(name) and
234+
result = name.toLowerCase())
235+
}
236+
237+
override RouteHandler getRouteHandler() {
238+
result = rh
239+
}
240+
241+
override string getKind() {
242+
result = "header"
243+
}
226244
}
227245

228246
/**

0 commit comments

Comments
 (0)