Skip to content

Commit 6e09a65

Browse files
committed
Added support for NextRequest middleware SSRF.
1 parent 734ad2d commit 6e09a65

File tree

3 files changed

+14
-4
lines changed

3 files changed

+14
-4
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,12 @@ module NextJS {
281281
*/
282282
class NextAppRouteHandler extends DataFlow::FunctionNode, Http::Servers::StandardRouteHandler {
283283
NextAppRouteHandler() {
284-
exists(Module mod | mod.getFile().getParentContainer() = apiFolder() |
285-
this = mod.getAnExportedValue(any(Http::RequestMethodName m)).getAFunctionValue() and
284+
exists(Module mod |
285+
mod.getFile().getParentContainer() = apiFolder() or
286+
mod.getFile().getBaseName() = ["middleware.ts", "middleware.js"]
287+
|
288+
this =
289+
mod.getAnExportedValue([any(Http::RequestMethodName m), "middleware"]).getAFunctionValue() and
286290
(
287291
this.getParameter(0).hasUnderlyingType("next/server", "NextRequest")
288292
or

javascript/ql/test/query-tests/Security/CWE-918/Request/middleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { NextRequest, NextResponse } from 'next/server';
22

33
export async function middleware(req: NextRequest) {
4-
const target = req.nextUrl // $ MISSING : Source[js/request-forgery]
4+
const target = req.nextUrl // $ Source[js/request-forgery]
55
if (target) {
6-
const res = await fetch(target) // $ MISSING: Alert[js/request-forgery] Sink[js/request-forgery]
6+
const res = await fetch(target) // $ Alert[js/request-forgery] Sink[js/request-forgery]
77
const data = await res.text()
88
return new NextResponse(data)
99
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#select
22
| Request/app/api/proxy/route2.serverSide.ts:5:21:5:30 | fetch(url) | Request/app/api/proxy/route2.serverSide.ts:4:25:4:34 | req.json() | Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | The $@ of this request depends on a $@. | Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | URL | Request/app/api/proxy/route2.serverSide.ts:4:25:4:34 | req.json() | user-provided value |
33
| Request/app/api/proxy/route.serverSide.ts:3:21:3:30 | fetch(url) | Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | The $@ of this request depends on a $@. | Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | URL | Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | user-provided value |
4+
| Request/middleware.ts:6:25:6:37 | fetch(target) | Request/middleware.ts:4:20:4:30 | req.nextUrl | Request/middleware.ts:6:31:6:36 | target | The $@ of this request depends on a $@. | Request/middleware.ts:6:31:6:36 | target | URL | Request/middleware.ts:4:20:4:30 | req.nextUrl | user-provided value |
45
| 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 |
56
| apollo.serverSide.ts:18:37:18:62 | get(fil ... => {}) | apollo.serverSide.ts:17:34:17:42 | { files } | apollo.serverSide.ts:18:41:18:48 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:18:41:18:48 | file.url | URL | apollo.serverSide.ts:17:34:17:42 | { files } | user-provided value |
67
| 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 |
@@ -37,6 +38,8 @@ edges
3738
| Request/app/api/proxy/route.serverSide.ts:2:9:2:34 | url | Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | provenance | |
3839
| Request/app/api/proxy/route.serverSide.ts:2:19:2:34 | await req.json() | Request/app/api/proxy/route.serverSide.ts:2:9:2:15 | { url } | provenance | |
3940
| Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | Request/app/api/proxy/route.serverSide.ts:2:19:2:34 | await req.json() | provenance | |
41+
| Request/middleware.ts:4:11:4:30 | target | Request/middleware.ts:6:31:6:36 | target | provenance | |
42+
| Request/middleware.ts:4:20:4:30 | req.nextUrl | Request/middleware.ts:4:11:4:30 | target | provenance | |
4043
| apollo.serverSide.ts:7:36:7:44 | files | apollo.serverSide.ts:8:13:8:17 | files | provenance | |
4144
| apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:7:36:7:44 | files | provenance | |
4245
| apollo.serverSide.ts:8:13:8:17 | files | apollo.serverSide.ts:8:28:8:31 | file | provenance | |
@@ -111,6 +114,9 @@ nodes
111114
| Request/app/api/proxy/route.serverSide.ts:2:19:2:34 | await req.json() | semmle.label | await req.json() |
112115
| Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | semmle.label | req.json() |
113116
| Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | semmle.label | url |
117+
| Request/middleware.ts:4:11:4:30 | target | semmle.label | target |
118+
| Request/middleware.ts:4:20:4:30 | req.nextUrl | semmle.label | req.nextUrl |
119+
| Request/middleware.ts:6:31:6:36 | target | semmle.label | target |
114120
| apollo.serverSide.ts:7:36:7:44 | files | semmle.label | files |
115121
| apollo.serverSide.ts:7:36:7:44 | { files } | semmle.label | { files } |
116122
| apollo.serverSide.ts:8:13:8:17 | files | semmle.label | files |

0 commit comments

Comments
 (0)