Skip to content

Commit b11b714

Browse files
authored
Merge pull request #696 from esben-semmle/js/host-request-forgery
Approved by asger-semmle
2 parents e15481a + 3cd6223 commit b11b714

File tree

13 files changed

+100
-10
lines changed

13 files changed

+100
-10
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
|--------------------------------------------|------------------------------|------------------------------------------------------------------------------|
2727
| Client-side cross-site scripting | More true-positive results, fewer false-positive results. | This rule now recognizes WinJS functions that are vulnerable to HTML injection, and no longer flags certain safe uses of jQuery. |
2828
| Insecure randomness | More results | This rule now flags insecure uses of `crypto.pseudoRandomBytes`. |
29+
| Uncontrolled data used in network request | More results | This rule now recognizes host values that are vulnerable to injection. |
2930
| Unused parameter | Fewer false-positive results | This rule no longer flags parameters with leading underscore. |
3031
| Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that are implictly used by JSX elements, and no longer flags variables with leading underscore. |
3132

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

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ abstract class CustomClientRequest extends DataFlow::InvokeNode {
2121
*/
2222
abstract DataFlow::Node getUrl();
2323

24+
/**
25+
* Gets the host of the request.
26+
*/
27+
abstract DataFlow::Node getHost();
28+
2429
/**
2530
* Gets a node that contributes to the data-part this request.
2631
*/
@@ -50,6 +55,13 @@ class ClientRequest extends DataFlow::InvokeNode {
5055
result = custom.getUrl()
5156
}
5257

58+
/**
59+
* Gets the host of the request.
60+
*/
61+
DataFlow::Node getHost() {
62+
result = custom.getHost()
63+
}
64+
5365
/**
5466
* Gets a node that contributes to the data-part this request.
5567
*/
@@ -67,7 +79,7 @@ private string httpMethodName() {
6779
}
6880

6981
/**
70-
* Gets the name of a property that likely contains a URL value.
82+
* Gets the name of a property that likely contains a URL value.
7183
*/
7284
private string urlPropertyName() {
7385
result = "uri" or
@@ -93,16 +105,17 @@ private class RequestUrlRequest extends CustomClientRequest {
93105
(
94106
callee = DataFlow::moduleImport(moduleName) or
95107
callee = DataFlow::moduleMember(moduleName, httpMethodName())
96-
) and
97-
(
98-
url = getArgument(0) or
99-
url = getOptionArgument(0, urlPropertyName())
100108
)
101109
)
102110
}
103111

104112
override DataFlow::Node getUrl() {
105-
result = url
113+
result = getArgument(0) or
114+
result = getOptionArgument(0, urlPropertyName())
115+
}
116+
117+
override DataFlow::Node getHost() {
118+
none()
106119
}
107120

108121
override DataFlow::Node getADataNode() {
@@ -129,10 +142,18 @@ private class AxiosUrlRequest extends CustomClientRequest {
129142
)
130143
}
131144

145+
private DataFlow::Node getOptionArgument(string name) {
146+
// depends on the method name and the call arity, over-approximating slightly in the name of simplicity
147+
result = getOptionArgument([0..2], name)
148+
}
149+
132150
override DataFlow::Node getUrl() {
133151
result = getArgument(0) or
134-
// depends on the method name and the call arity, over-approximating slightly in the name of simplicity
135-
result = getOptionArgument([0..2], urlPropertyName())
152+
result = getOptionArgument(urlPropertyName())
153+
}
154+
155+
override DataFlow::Node getHost() {
156+
result = getOptionArgument("host")
136157
}
137158

138159
override DataFlow::Node getADataNode() {
@@ -179,6 +200,8 @@ private class FetchUrlRequest extends CustomClientRequest {
179200
result = url
180201
}
181202

203+
override DataFlow::Node getHost() { none() }
204+
182205
override DataFlow::Node getADataNode() {
183206
exists (string name |
184207
name = "headers" or name = "body" |
@@ -209,6 +232,14 @@ private class GotUrlRequest extends CustomClientRequest {
209232
not exists (getOptionArgument(1, "baseUrl"))
210233
}
211234

235+
override DataFlow::Node getHost() {
236+
exists (string name |
237+
name = "host" or
238+
name = "hostname" |
239+
result = getOptionArgument(1, name)
240+
)
241+
}
242+
212243
override DataFlow::Node getADataNode() {
213244
exists (string name |
214245
name = "headers" or name = "body" or name = "query" |
@@ -238,6 +269,8 @@ private class SuperAgentUrlRequest extends CustomClientRequest {
238269
result = url
239270
}
240271

272+
override DataFlow::Node getHost() { none() }
273+
241274
override DataFlow::Node getADataNode() {
242275
exists (string name |
243276
name = "set" or name = "send" or name = "query" |
@@ -255,5 +288,6 @@ private class XMLHttpRequest extends CustomClientRequest {
255288

256289
override DataFlow::Node getUrl() { result = getAMethodCall("open").getArgument(1) }
257290

291+
override DataFlow::Node getHost() { none() }
258292
override DataFlow::Node getADataNode() { result = getAMethodCall("send").getArgument(0) }
259293
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ module Electron {
6464
result = getOptionArgument(0, "url")
6565
}
6666

67+
override DataFlow::Node getHost() {
68+
exists (string name |
69+
name = "host" or
70+
name = "hostname" |
71+
result = getOptionArgument(0, name)
72+
)
73+
}
74+
6775
override DataFlow::Node getADataNode() {
6876
exists (string name |
6977
name = "write" or name = "end" |

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,14 @@ module NodeJSLib {
737737
result = url
738738
}
739739

740+
override DataFlow::Node getHost() {
741+
exists (string name |
742+
name = "host" or
743+
name = "hostname" |
744+
result = getOptionArgument(1, name)
745+
)
746+
}
747+
740748
override DataFlow::Node getADataNode() {
741749
exists (string name |
742750
name = "write" or name = "end" |

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,5 +359,7 @@ private class JQueryClientRequest extends CustomClientRequest {
359359
result = getOptionArgument([0 .. 1], "url")
360360
}
361361

362+
override DataFlow::Node getHost() { none() }
363+
362364
override DataFlow::Node getADataNode() { result = getOptionArgument([0 .. 1], "data") }
363365
}

javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,20 @@ module RequestForgery {
7272

7373
ClientRequest request;
7474

75+
string kind;
76+
7577
ClientRequestUrlAsSink() {
76-
this = request.getUrl()
78+
this = request.getUrl() and kind = "URL" or
79+
this = request.getHost() and kind = "host"
7780
}
7881

7982
override DataFlow::Node getARequest() {
8083
result = request
8184
}
8285

8386
override string getKind() {
84-
result = "URL"
87+
result = kind
8588
}
89+
8690
}
8791
}

javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@
3232
| tst.js:77:5:77:32 | $.getJS ... data}) |
3333
| tst.js:78:5:78:38 | $.getJS ... data}) |
3434
| tst.js:80:15:80:34 | new XMLHttpRequest() |
35+
| tst.js:87:5:87:39 | http.ge ... host}) |
36+
| tst.js:89:5:89:23 | axios({host: host}) |
37+
| tst.js:91:5:91:34 | got(rel ... host}) |
38+
| tst.js:93:5:93:35 | net.req ... host }) |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
2+
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
3+
| tst.js:91:5:91:34 | got(rel ... host}) | tst.js:91:29:91:32 | host |
4+
| tst.js:93:5:93:35 | net.req ... host }) | tst.js:93:29:93:32 | host |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import javascript
2+
3+
from ClientRequest r
4+
select r, r.getHost()

javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@
3838
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:15:78:37 | {url: u ... : data} |
3939
| tst.js:78:5:78:38 | $.getJS ... data}) | tst.js:78:21:78:23 | url |
4040
| tst.js:80:15:80:34 | new XMLHttpRequest() | tst.js:81:17:81:19 | url |
41+
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:14:87:24 | relativeUrl |
42+
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:11:89:22 | {host: host} |
43+
| tst.js:91:5:91:34 | got(rel ... host}) | tst.js:91:9:91:19 | relativeUrl |
44+
| tst.js:93:5:93:35 | net.req ... host }) | tst.js:93:17:93:34 | { hostname: host } |

0 commit comments

Comments
 (0)