Skip to content

Commit 261d28a

Browse files
authored
Merge pull request #19099 from Napalys/js/axios_missing_methods
JS: Added support for missing `axios` methods
2 parents 2aee47b + 0689cf7 commit 261d28a

File tree

9 files changed

+123
-1
lines changed

9 files changed

+123
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
Enhanced `axios` support with new methods (`postForm`, `putForm`, `patchForm`, `getUri`, `create`) and added support for `interceptors.request` and `interceptors.response`.

javascript/ql/lib/ext/axios.model.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: sinkModel
5+
data:
6+
- ["axios", "Member[interceptors].Member[request].Member[use].Argument[0].Parameter[0].Member[url]", "request-forgery"]
7+
8+
- addsTo:
9+
pack: codeql/javascript-all
10+
extensible: sourceModel
11+
data:
12+
- ["axios", "Member[interceptors].Member[response].Member[use].Argument[0].Parameter[0]", "response"]

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ module ClientRequest {
222222
method = "request"
223223
or
224224
this = axios().getMember(method).getACall() and
225-
method = [httpMethodName(), "request"]
225+
method = [httpMethodName(), "request", "postForm", "putForm", "patchForm", "getUri"]
226+
or
227+
this = axios().getMember("create").getReturn().getACall() and
228+
method = "request"
226229
}
227230

228231
private int getOptionsArgIndex() {
@@ -254,6 +257,8 @@ module ClientRequest {
254257
method = ["post", "put"] and
255258
result = [this.getArgument(1), this.getOptionArgument(2, "data")]
256259
or
260+
method = ["postForm", "putForm", "patchForm"] and result = this.getArgument(1)
261+
or
257262
result = this.getOptionArgument([0 .. 2], ["headers", "params"])
258263
}
259264

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ test_ClientRequest
103103
| tst.js:334:5:334:25 | got.pag ... rl, {}) |
104104
| tst.js:337:5:337:20 | jsonClient.get() |
105105
| tst.js:340:5:340:21 | jsonClient2.get() |
106+
| tst.js:344:5:344:37 | axios.p ... config) |
107+
| tst.js:345:5:345:28 | axios.p ... , data) |
108+
| tst.js:346:5:346:36 | axios.p ... config) |
109+
| tst.js:347:5:347:30 | axios.p ... , data) |
110+
| tst.js:348:5:348:38 | axios.p ... config) |
111+
| tst.js:349:5:349:30 | axios.g ... url }) |
112+
| tst.js:352:5:352:66 | axiosIn ... text"}) |
106113
test_getADataNode
107114
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:15:18:15:55 | { 'Cont ... json' } |
108115
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:16:15:16:35 | {x: 'te ... 'test'} |
@@ -146,6 +153,11 @@ test_getADataNode
146153
| tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:255:25:255:35 | 'new_value' |
147154
| tst.js:286:20:286:55 | new Web ... :8080') | tst.js:288:21:288:35 | 'Hello Server!' |
148155
| tst.js:321:5:321:32 | superag ... st(url) | tst.js:321:39:321:42 | data |
156+
| tst.js:344:5:344:37 | axios.p ... config) | tst.js:344:25:344:28 | data |
157+
| tst.js:345:5:345:28 | axios.p ... , data) | tst.js:345:24:345:27 | data |
158+
| tst.js:346:5:346:36 | axios.p ... config) | tst.js:346:24:346:27 | data |
159+
| tst.js:347:5:347:30 | axios.p ... , data) | tst.js:347:26:347:29 | data |
160+
| tst.js:348:5:348:38 | axios.p ... config) | tst.js:348:26:348:29 | data |
149161
test_getHost
150162
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
151163
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
@@ -268,6 +280,14 @@ test_getUrl
268280
| tst.js:337:5:337:20 | jsonClient.get() | tst.js:336:41:336:43 | url |
269281
| tst.js:340:5:340:21 | jsonClient2.get() | tst.js:339:42:339:44 | url |
270282
| tst.js:340:5:340:21 | jsonClient2.get() | tst.js:339:61:339:63 | url |
283+
| tst.js:344:5:344:37 | axios.p ... config) | tst.js:344:20:344:22 | url |
284+
| tst.js:345:5:345:28 | axios.p ... , data) | tst.js:345:19:345:21 | url |
285+
| tst.js:346:5:346:36 | axios.p ... config) | tst.js:346:19:346:21 | url |
286+
| tst.js:347:5:347:30 | axios.p ... , data) | tst.js:347:21:347:23 | url |
287+
| tst.js:348:5:348:38 | axios.p ... config) | tst.js:348:21:348:23 | url |
288+
| tst.js:349:5:349:30 | axios.g ... url }) | tst.js:349:18:349:29 | { url: url } |
289+
| tst.js:352:5:352:66 | axiosIn ... text"}) | tst.js:352:19:352:65 | {method ... "text"} |
290+
| tst.js:352:5:352:66 | axiosIn ... text"}) | tst.js:352:40:352:42 | url |
271291
test_getAResponseDataNode
272292
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:4:5:7:6 | axios({ ... \\n }) | json | true |
273293
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:12:5:17:6 | axios({ ... \\n }) | json | true |
@@ -354,3 +374,10 @@ test_getAResponseDataNode
354374
| tst.js:334:5:334:25 | got.pag ... rl, {}) | tst.js:334:5:334:25 | got.pag ... rl, {}) | text | true |
355375
| tst.js:337:5:337:20 | jsonClient.get() | tst.js:337:5:337:20 | jsonClient.get() | text | true |
356376
| tst.js:340:5:340:21 | jsonClient2.get() | tst.js:340:5:340:21 | jsonClient2.get() | text | true |
377+
| tst.js:344:5:344:37 | axios.p ... config) | tst.js:344:5:344:37 | axios.p ... config) | json | true |
378+
| tst.js:345:5:345:28 | axios.p ... , data) | tst.js:345:5:345:28 | axios.p ... , data) | json | true |
379+
| tst.js:346:5:346:36 | axios.p ... config) | tst.js:346:5:346:36 | axios.p ... config) | json | true |
380+
| tst.js:347:5:347:30 | axios.p ... , data) | tst.js:347:5:347:30 | axios.p ... , data) | json | true |
381+
| tst.js:348:5:348:38 | axios.p ... config) | tst.js:348:5:348:38 | axios.p ... config) | json | true |
382+
| tst.js:349:5:349:30 | axios.g ... url }) | tst.js:349:5:349:30 | axios.g ... url }) | json | true |
383+
| tst.js:352:5:352:66 | axiosIn ... text"}) | tst.js:352:5:352:66 | axiosIn ... text"}) | text | true |

javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,15 @@ function gotTests(url){
339339
const jsonClient2 = got.extend({url: url}).extend({url: url});
340340
jsonClient2.get();
341341
}
342+
343+
function moreAxiosTests(url, data, config){
344+
axios.postForm(url, data, config);
345+
axios.putForm(url, data);
346+
axios.putForm(url, data, config);
347+
axios.patchForm(url, data);
348+
axios.patchForm(url, data, config);
349+
axios.getUri({ url: url });
350+
351+
const axiosInstance = axios.create({});
352+
axiosInstance({method: "get", url: url, responseType: "text"});
353+
}

javascript/ql/test/query-tests/Security/CWE-079/DomBasedXssWithResponseThreat/Xss.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#select
2+
| interceptors.js:9:56:9:72 | userGeneratedHtml | interceptors.js:7:6:7:13 | response | interceptors.js:9:56:9:72 | userGeneratedHtml | Cross-site scripting vulnerability due to $@. | interceptors.js:7:6:7:13 | response | user-provided value |
23
| test.jsx:27:29:27:32 | data | test.jsx:5:28:5:63 | fetch(" ... ntent") | test.jsx:27:29:27:32 | data | Cross-site scripting vulnerability due to $@. | test.jsx:5:28:5:63 | fetch(" ... ntent") | user-provided value |
34
| test.ts:21:57:21:76 | response.description | test.ts:8:9:8:79 | this.#h ... query') | test.ts:21:57:21:76 | response.description | Cross-site scripting vulnerability due to $@. | test.ts:8:9:8:79 | this.#h ... query') | user-provided value |
45
| test.ts:24:36:24:90 | `<h2>${ ... o}</p>` | test.ts:8:9:8:79 | this.#h ... query') | test.ts:24:36:24:90 | `<h2>${ ... o}</p>` | Cross-site scripting vulnerability due to $@. | test.ts:8:9:8:79 | this.#h ... query') | user-provided value |
@@ -18,6 +19,9 @@
1819
| testUseQueries2.vue:40:10:40:23 | v-html=data3 | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | testUseQueries2.vue:40:10:40:23 | v-html=data3 | Cross-site scripting vulnerability due to $@. | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | user-provided value |
1920
| testUseQueries.vue:25:10:25:23 | v-html=data2 | testUseQueries.vue:11:36:11:49 | fetch("${id}") | testUseQueries.vue:25:10:25:23 | v-html=data2 | Cross-site scripting vulnerability due to $@. | testUseQueries.vue:11:36:11:49 | fetch("${id}") | user-provided value |
2021
edges
22+
| interceptors.js:7:6:7:13 | response | interceptors.js:8:35:8:42 | response | provenance | |
23+
| interceptors.js:8:15:8:47 | userGeneratedHtml | interceptors.js:9:56:9:72 | userGeneratedHtml | provenance | |
24+
| interceptors.js:8:35:8:42 | response | interceptors.js:8:15:8:47 | userGeneratedHtml | provenance | |
2125
| test.jsx:5:11:5:63 | response | test.jsx:6:24:6:31 | response | provenance | |
2226
| test.jsx:5:22:5:63 | await f ... ntent") | test.jsx:5:11:5:63 | response | provenance | |
2327
| test.jsx:5:28:5:63 | fetch(" ... ntent") | test.jsx:5:22:5:63 | await f ... ntent") | provenance | |
@@ -96,6 +100,10 @@ edges
96100
| testUseQueries.vue:12:20:12:34 | response.json() | testUseQueries.vue:18:22:18:36 | results[0].data | provenance | |
97101
| testUseQueries.vue:18:22:18:36 | results[0].data | testUseQueries.vue:25:10:25:23 | v-html=data2 | provenance | |
98102
nodes
103+
| interceptors.js:7:6:7:13 | response | semmle.label | response |
104+
| interceptors.js:8:15:8:47 | userGeneratedHtml | semmle.label | userGeneratedHtml |
105+
| interceptors.js:8:35:8:42 | response | semmle.label | response |
106+
| interceptors.js:9:56:9:72 | userGeneratedHtml | semmle.label | userGeneratedHtml |
99107
| test.jsx:5:11:5:63 | response | semmle.label | response |
100108
| test.jsx:5:22:5:63 | await f ... ntent") | semmle.label | await f ... ntent") |
101109
| test.jsx:5:28:5:63 | fetch(" ... ntent") | semmle.label | fetch(" ... ntent") |
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const express = require("express");
2+
const axios = require("axios");
3+
4+
const app = express();
5+
6+
axios.interceptors.response.use(
7+
(response) => { // $ Source
8+
const userGeneratedHtml = response.data;
9+
document.getElementById("content").innerHTML = userGeneratedHtml; // $ Alert
10+
return response;
11+
},
12+
(error) => {
13+
return Promise.reject(error);
14+
}
15+
);
16+
17+
app.post("/fetch", (req, res) => {
18+
const { url } = req.body;
19+
axios.get(url);
20+
});

javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#select
22
| apollo.serverSide.ts:8:39:8:64 | get(fil ... => {}) | apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:8:43:8:50 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:8:43:8:50 | file.url | URL | apollo.serverSide.ts:7:36:7:44 | { files } | user-provided value |
3+
| axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | The $@ of this request depends on a $@. | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | endpoint | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | user-provided value |
34
| serverSide.js:18:5:18:20 | request(tainted) | serverSide.js:14:29:14:35 | req.url | serverSide.js:18:13:18:19 | tainted | The $@ of this request depends on a $@. | serverSide.js:18:13:18:19 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value |
45
| serverSide.js:20:5:20:24 | request.get(tainted) | serverSide.js:14:29:14:35 | req.url | serverSide.js:20:17:20:23 | tainted | The $@ of this request depends on a $@. | serverSide.js:20:17:20:23 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value |
56
| serverSide.js:24:5:24:20 | request(options) | serverSide.js:14:29:14:35 | req.url | serverSide.js:23:19:23:25 | tainted | The $@ of this request depends on a $@. | serverSide.js:23:19:23:25 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value |
@@ -30,6 +31,11 @@ edges
3031
| apollo.serverSide.ts:8:13:8:17 | files | apollo.serverSide.ts:8:28:8:31 | file | provenance | |
3132
| apollo.serverSide.ts:8:28:8:31 | file | apollo.serverSide.ts:8:43:8:46 | file | provenance | |
3233
| apollo.serverSide.ts:8:43:8:46 | file | apollo.serverSide.ts:8:43:8:50 | file.url | provenance | |
34+
| axiosInterceptors.serverSide.js:19:11:19:17 | { url } | axiosInterceptors.serverSide.js:19:11:19:28 | url | provenance | |
35+
| axiosInterceptors.serverSide.js:19:11:19:28 | url | axiosInterceptors.serverSide.js:20:23:20:25 | url | provenance | |
36+
| axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:19:11:19:17 | { url } | provenance | |
37+
| axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | provenance | |
38+
| axiosInterceptors.serverSide.js:20:23:20:25 | url | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | provenance | |
3339
| serverSide.js:14:9:14:52 | tainted | serverSide.js:18:13:18:19 | tainted | provenance | |
3440
| serverSide.js:14:9:14:52 | tainted | serverSide.js:20:17:20:23 | tainted | provenance | |
3541
| serverSide.js:14:9:14:52 | tainted | serverSide.js:23:19:23:25 | tainted | provenance | |
@@ -85,6 +91,12 @@ nodes
8591
| apollo.serverSide.ts:8:28:8:31 | file | semmle.label | file |
8692
| apollo.serverSide.ts:8:43:8:46 | file | semmle.label | file |
8793
| apollo.serverSide.ts:8:43:8:50 | file.url | semmle.label | file.url |
94+
| axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | semmle.label | userProvidedUrl |
95+
| axiosInterceptors.serverSide.js:19:11:19:17 | { url } | semmle.label | { url } |
96+
| axiosInterceptors.serverSide.js:19:11:19:28 | url | semmle.label | url |
97+
| axiosInterceptors.serverSide.js:19:21:19:28 | req.body | semmle.label | req.body |
98+
| axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | semmle.label | userProvidedUrl |
99+
| axiosInterceptors.serverSide.js:20:23:20:25 | url | semmle.label | url |
88100
| serverSide.js:14:9:14:52 | tainted | semmle.label | tainted |
89101
| serverSide.js:14:19:14:42 | url.par ... , true) | semmle.label | url.par ... , true) |
90102
| serverSide.js:14:29:14:35 | req.url | semmle.label | req.url |
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const express = require("express");
2+
const axios = require("axios");
3+
4+
const app = express();
5+
6+
let userProvidedUrl = "";
7+
8+
axios.interceptors.request.use(
9+
function (config) {
10+
if (userProvidedUrl) {
11+
config.url = userProvidedUrl; // $ Alert[js/request-forgery]
12+
}
13+
return config;
14+
},
15+
error => error
16+
);
17+
18+
app.post("/fetch", (req, res) => {
19+
const { url } = req.body; // $ Source[js/request-forgery]
20+
userProvidedUrl = url;
21+
axios.get("placeholder");
22+
});

0 commit comments

Comments
 (0)