Skip to content

Commit 8225d99

Browse files
author
Esben Sparre Andreasen
committed
JS: split ClientSideUrlRedirect.qll
1 parent c3973c0 commit 8225d99

File tree

2 files changed

+148
-131
lines changed

2 files changed

+148
-131
lines changed
Lines changed: 7 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,18 @@
11
/**
2-
* Provides a taint-tracking configuration for reasoning about unvalidated URL
3-
* redirection problems on the client side.
2+
* Provides a taint-tracking configuration for reasoning about
3+
* unvalidated URL redirection problems on the client side.
4+
*
5+
* Note, for performance reasons: only import this file if
6+
* `ClientSideUrlRedirect::Configuration` is needed, otherwise
7+
* `ClientSideUrlRedirectCustomizations` should be imported instead.
48
*/
59

610
import javascript
711
import semmle.javascript.security.dataflow.RemoteFlowSources
812
import UrlConcatenation
913

1014
module ClientSideUrlRedirect {
11-
/**
12-
* A data flow source for unvalidated URL redirect vulnerabilities.
13-
*/
14-
abstract class Source extends DataFlow::Node { }
15-
16-
/**
17-
* A data flow sink for unvalidated URL redirect vulnerabilities.
18-
*/
19-
abstract class Sink extends DataFlow::Node { }
20-
21-
/**
22-
* A sanitizer for unvalidated URL redirect vulnerabilities.
23-
*/
24-
abstract class Sanitizer extends DataFlow::Node { }
25-
26-
/**
27-
* A flow label for values that represent the URL of the current document, and
28-
* hence are only partially user-controlled.
29-
*/
30-
class DocumentUrl extends DataFlow::FlowLabel {
31-
DocumentUrl() { this = "document.url" }
32-
}
15+
import ClientSideUrlRedirectCustomizations::ClientSideUrlRedirect
3316

3417
/**
3518
* A taint-tracking configuration for reasoning about unvalidated URL redirections.
@@ -68,111 +51,4 @@ module ClientSideUrlRedirect {
6851
succ.(DataFlow::PropRead).accesses(pred, "href")
6952
}
7053
}
71-
72-
/** A source of remote user input, considered as a flow source for unvalidated URL redirects. */
73-
class RemoteFlowSourceAsSource extends Source {
74-
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
75-
}
76-
77-
/**
78-
* Holds if `queryAccess` is an expression that may access the query string
79-
* of a URL that flows into `nd` (that is, the part after the `?`).
80-
*/
81-
private predicate queryAccess(DataFlow::Node nd, DataFlow::Node queryAccess) {
82-
exists(string propertyName |
83-
queryAccess.asExpr().(PropAccess).accesses(nd.asExpr(), propertyName)
84-
|
85-
propertyName = "search" or propertyName = "hash"
86-
)
87-
or
88-
exists(MethodCallExpr mce, string methodName |
89-
mce = queryAccess.asExpr() and mce.calls(nd.asExpr(), methodName)
90-
|
91-
methodName = "split" and
92-
// exclude `location.href.split('?')[0]`, which can never refer to the query string
93-
not exists(PropAccess pacc | mce = pacc.getBase() | pacc.getPropertyName() = "0")
94-
or
95-
(methodName = "substring" or methodName = "substr") and
96-
// exclude `location.href.substring(0, ...)` and similar, which can
97-
// never refer to the query string
98-
not mce.getArgument(0).(NumberLiteral).getIntValue() = 0
99-
)
100-
or
101-
exists(MethodCallExpr mce |
102-
queryAccess.asExpr() = mce and
103-
mce = any(RegExpLiteral re).flow().(DataFlow::SourceNode).getAMethodCall("exec").asExpr() and
104-
nd.asExpr() = mce.getArgument(0)
105-
)
106-
}
107-
108-
/**
109-
* A sink which is used to set the window location.
110-
*/
111-
class LocationSink extends Sink, DataFlow::ValueNode {
112-
LocationSink() {
113-
// A call to a `window.navigate` or `window.open`
114-
exists(string name |
115-
name = "navigate" or
116-
name = "open" or
117-
name = "openDialog" or
118-
name = "showModalDialog"
119-
|
120-
this = DataFlow::globalVarRef(name).getACall().getArgument(0)
121-
)
122-
or
123-
// A call to `location.replace` or `location.assign`
124-
exists(DataFlow::MethodCallNode locationCall, string name |
125-
locationCall = DOM::locationRef().getAMethodCall(name) and
126-
this = locationCall.getArgument(0)
127-
|
128-
name = "replace" or name = "assign"
129-
)
130-
or
131-
// An assignment to `location`
132-
exists(Assignment assgn | isLocation(assgn.getTarget()) and astNode = assgn.getRhs())
133-
or
134-
// An assignment to `location.href`, `location.protocol` or `location.hostname`
135-
exists(DataFlow::PropWrite pw, string propName |
136-
pw = DOM::locationRef().getAPropertyWrite(propName) and
137-
this = pw.getRhs()
138-
|
139-
propName = "href" or propName = "protocol" or propName = "hostname"
140-
)
141-
or
142-
// A redirection using the AngularJS `$location` service
143-
exists(AngularJS::ServiceReference service |
144-
service.getName() = "$location" and
145-
this.asExpr() = service.getAMethodCall("url").getArgument(0)
146-
)
147-
}
148-
}
149-
150-
/**
151-
* An expression that may be interpreted as the URL of a script.
152-
*/
153-
abstract class ScriptUrlSink extends Sink { }
154-
155-
/**
156-
* An argument expression to `new Worker(...)`, viewed as
157-
* a `ScriptUrlSink`.
158-
*/
159-
class WebWorkerScriptUrlSink extends ScriptUrlSink, DataFlow::ValueNode {
160-
WebWorkerScriptUrlSink() {
161-
this = DataFlow::globalVarRef("Worker").getAnInstantiation().getArgument(0)
162-
}
163-
}
164-
165-
/**
166-
* A script or iframe `src` attribute, viewed as a `ScriptUrlSink`.
167-
*/
168-
class SrcAttributeUrlSink extends ScriptUrlSink, DataFlow::ValueNode {
169-
SrcAttributeUrlSink() {
170-
exists(DOM::AttributeDefinition attr, string eltName |
171-
attr.getElement().getName() = eltName and
172-
(eltName = "script" or eltName = "iframe") and
173-
attr.getName() = "src" and
174-
this = attr.getValueNode()
175-
)
176-
}
177-
}
17854
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Provides default sources, sinks and sanitisers for reasoning about
3+
* unvalidated URL redirection problems on the client side, as well as
4+
* extension points for adding your own.
5+
*/
6+
7+
import javascript
8+
import semmle.javascript.security.dataflow.RemoteFlowSources
9+
import UrlConcatenation
10+
11+
module ClientSideUrlRedirect {
12+
/**
13+
* A data flow source for unvalidated URL redirect vulnerabilities.
14+
*/
15+
abstract class Source extends DataFlow::Node { }
16+
17+
/**
18+
* A data flow sink for unvalidated URL redirect vulnerabilities.
19+
*/
20+
abstract class Sink extends DataFlow::Node { }
21+
22+
/**
23+
* A sanitizer for unvalidated URL redirect vulnerabilities.
24+
*/
25+
abstract class Sanitizer extends DataFlow::Node { }
26+
27+
/**
28+
* A flow label for values that represent the URL of the current document, and
29+
* hence are only partially user-controlled.
30+
*/
31+
class DocumentUrl extends DataFlow::FlowLabel {
32+
DocumentUrl() { this = "document.url" }
33+
}
34+
35+
/** A source of remote user input, considered as a flow source for unvalidated URL redirects. */
36+
class RemoteFlowSourceAsSource extends Source {
37+
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
38+
}
39+
40+
/**
41+
* Holds if `queryAccess` is an expression that may access the query string
42+
* of a URL that flows into `nd` (that is, the part after the `?`).
43+
*/
44+
predicate queryAccess(DataFlow::Node nd, DataFlow::Node queryAccess) {
45+
exists(string propertyName |
46+
queryAccess.asExpr().(PropAccess).accesses(nd.asExpr(), propertyName)
47+
|
48+
propertyName = "search" or propertyName = "hash"
49+
)
50+
or
51+
exists(MethodCallExpr mce, string methodName |
52+
mce = queryAccess.asExpr() and mce.calls(nd.asExpr(), methodName)
53+
|
54+
methodName = "split" and
55+
// exclude `location.href.split('?')[0]`, which can never refer to the query string
56+
not exists(PropAccess pacc | mce = pacc.getBase() | pacc.getPropertyName() = "0")
57+
or
58+
(methodName = "substring" or methodName = "substr") and
59+
// exclude `location.href.substring(0, ...)` and similar, which can
60+
// never refer to the query string
61+
not mce.getArgument(0).(NumberLiteral).getIntValue() = 0
62+
)
63+
or
64+
exists(MethodCallExpr mce |
65+
queryAccess.asExpr() = mce and
66+
mce = any(RegExpLiteral re).flow().(DataFlow::SourceNode).getAMethodCall("exec").asExpr() and
67+
nd.asExpr() = mce.getArgument(0)
68+
)
69+
}
70+
71+
/**
72+
* A sink which is used to set the window location.
73+
*/
74+
class LocationSink extends Sink, DataFlow::ValueNode {
75+
LocationSink() {
76+
// A call to a `window.navigate` or `window.open`
77+
exists(string name |
78+
name = "navigate" or
79+
name = "open" or
80+
name = "openDialog" or
81+
name = "showModalDialog"
82+
|
83+
this = DataFlow::globalVarRef(name).getACall().getArgument(0)
84+
)
85+
or
86+
// A call to `location.replace` or `location.assign`
87+
exists(DataFlow::MethodCallNode locationCall, string name |
88+
locationCall = DOM::locationRef().getAMethodCall(name) and
89+
this = locationCall.getArgument(0)
90+
|
91+
name = "replace" or name = "assign"
92+
)
93+
or
94+
// An assignment to `location`
95+
exists(Assignment assgn | isLocation(assgn.getTarget()) and astNode = assgn.getRhs())
96+
or
97+
// An assignment to `location.href`, `location.protocol` or `location.hostname`
98+
exists(DataFlow::PropWrite pw, string propName |
99+
pw = DOM::locationRef().getAPropertyWrite(propName) and
100+
this = pw.getRhs()
101+
|
102+
propName = "href" or propName = "protocol" or propName = "hostname"
103+
)
104+
or
105+
// A redirection using the AngularJS `$location` service
106+
exists(AngularJS::ServiceReference service |
107+
service.getName() = "$location" and
108+
this.asExpr() = service.getAMethodCall("url").getArgument(0)
109+
)
110+
}
111+
}
112+
113+
/**
114+
* An expression that may be interpreted as the URL of a script.
115+
*/
116+
abstract class ScriptUrlSink extends Sink { }
117+
118+
/**
119+
* An argument expression to `new Worker(...)`, viewed as
120+
* a `ScriptUrlSink`.
121+
*/
122+
class WebWorkerScriptUrlSink extends ScriptUrlSink, DataFlow::ValueNode {
123+
WebWorkerScriptUrlSink() {
124+
this = DataFlow::globalVarRef("Worker").getAnInstantiation().getArgument(0)
125+
}
126+
}
127+
128+
/**
129+
* A script or iframe `src` attribute, viewed as a `ScriptUrlSink`.
130+
*/
131+
class SrcAttributeUrlSink extends ScriptUrlSink, DataFlow::ValueNode {
132+
SrcAttributeUrlSink() {
133+
exists(DOM::AttributeDefinition attr, string eltName |
134+
attr.getElement().getName() = eltName and
135+
(eltName = "script" or eltName = "iframe") and
136+
attr.getName() = "src" and
137+
this = attr.getValueNode()
138+
)
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)