javascript.express.security.audit.express-ssrf.express-ssrf

Author
unknown
Download Count*
License
The following request $REQUEST.$METHOD() was found to be crafted from user-input $REQ
which can lead to Server-Side Request Forgery (SSRF) vulnerabilities. It is recommended where possible to not allow user-input to craft the base request, but to be treated as part of the path or query parameter. When user-input is necessary to craft the request, it is recommeneded to follow OWASP best practices to prevent abuse.
Run Locally
Run in CI
Defintion
rules:
- id: express-ssrf
message: "The following request $REQUEST.$METHOD() was found to be crafted from
user-input `$REQ` which can lead to Server-Side Request Forgery (SSRF)
vulnerabilities. It is recommended where possible to not allow user-input
to craft the base request, but to be treated as part of the path or query
parameter. When user-input is necessary to craft the request, it is
recommeneded to follow OWASP best practices to prevent abuse. "
metadata:
references:
- https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
cwe:
- "CWE-918: Server-Side Request Forgery (SSRF)"
technology:
- express
category: security
owasp:
- A10:2021 - Server-Side Request Forgery (SSRF)
cwe2022-top25: true
cwe2021-top25: true
subcategory:
- vuln
likelihood: MEDIUM
impact: HIGH
confidence: MEDIUM
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
languages:
- javascript
- typescript
severity: WARNING
mode: taint
options:
taint_unify_mvars: true
pattern-sources:
- patterns:
- pattern-either:
- pattern-inside: function ... ($REQ, ...) {...}
- pattern-either:
- pattern: $REQ.query
- pattern: $REQ.body
- pattern: $REQ.params
- pattern: $REQ.cookies
- pattern: $REQ.headers
- patterns:
- pattern-either:
- pattern-inside: |
({ $REQ }: Request,...) =>
{...}
- pattern-inside: |
({ $REQ }: $EXPRESS.Request,...) => {...}
- focus-metavariable: $REQ
- pattern-either:
- pattern: params
- pattern: query
- pattern: cookies
- pattern: headers
- pattern: body
pattern-sinks:
- patterns:
- pattern-either:
- pattern-inside: |
$REQUEST = require('request')
...
- pattern-inside: |
import * as $REQUEST from 'request'
...
- pattern-inside: |
import $REQUEST from 'request'
...
- pattern-either:
- pattern: $REQUEST.$METHOD("$HTTP"+$REQ. ... .$VALUE)
- pattern: $REQUEST.$METHOD("$HTTP"+$REQ. ... .$VALUE + $...A)
- pattern: $REQUEST.$METHOD(`$HTTP${$REQ. ... .$VALUE}...`)
- pattern: $REQUEST.$METHOD("$HTTP"+$REQ.$VALUE[...])
- pattern: $REQUEST.$METHOD("$HTTP"+$REQ.$VALUE[...] + $...A)
- pattern: $REQUEST.$METHOD(`$HTTP${$REQ.$VALUE[...]}...`)
- metavariable-regex:
metavariable: $METHOD
regex: ^(get|post|put|patch|del|head|delete)$
- metavariable-regex:
metavariable: $HTTP
regex: ^(https?:\/\/|//)$
- pattern-either:
- pattern: $REQ. ... .$VALUE
- patterns:
- pattern-either:
- pattern-inside: |
$REQUEST = require('request')
...
- pattern-inside: |
import * as $REQUEST from 'request'
...
- pattern-inside: |
import $REQUEST from 'request'
...
- pattern-either:
- pattern: $REQUEST.$METHOD($REQ. ... .$VALUE,...)
- pattern: $REQUEST.$METHOD($REQ. ... .$VALUE + $...A,...)
- pattern: $REQUEST.$METHOD(`${$REQ. ... .$VALUE}...`,...)
- pattern: $REQ. ... .$VALUE
- metavariable-regex:
metavariable: $METHOD
regex: ^(get|post|put|patch|del|head|delete)$
- patterns:
- pattern-either:
- pattern-inside: |
$REQUEST = require('request')
...
- pattern-inside: |
import * as $REQUEST from 'request'
...
- pattern-inside: |
import $REQUEST from 'request'
...
- pattern-either:
- pattern: $REQUEST.$METHOD($REQ.$VALUE['...'],...)
- pattern: $REQUEST.$METHOD($REQ.$VALUE['...'] + $...A,...)
- pattern: $REQUEST.$METHOD(`${$REQ.$VALUE['...']}...`,...)
- pattern: $REQ.$VALUE
- metavariable-regex:
metavariable: $METHOD
regex: ^(get|post|put|patch|del|head|delete)$
- patterns:
- pattern-either:
- pattern-inside: |
$REQUEST = require('request')
...
- pattern-inside: |
import * as $REQUEST from 'request'
...
- pattern-inside: |
import $REQUEST from 'request'
...
- pattern-either:
- pattern-inside: |
$ASSIGN = $REQ. ... .$VALUE
...
- pattern-inside: |
$ASSIGN = $REQ. ... .$VALUE['...']
...
- pattern-inside: |
$ASSIGN = $REQ. ... .$VALUE + $...A
...
- pattern-inside: |
$ASSIGN = $REQ. ... .$VALUE['...'] + $...A
...
- pattern-inside: |
$ASSIGN = `${$REQ. ... .$VALUE}...`
...
- pattern-inside: |
$ASSIGN = `${$REQ. ... .$VALUE['...']}...`
...
- patterns:
- pattern-either:
- pattern-inside: |
$ASSIGN = "$HTTP"+ $REQ. ... .$VALUE
...
- pattern-inside: |
$ASSIGN = "$HTTP"+$REQ. ... .$VALUE + $...A
...
- pattern-inside: |
$ASSIGN = "$HTTP"+$REQ.$VALUE[...]
...
- pattern-inside: |
$ASSIGN = "$HTTP"+$REQ.$VALUE[...] + $...A
...
- pattern-inside: |
$ASSIGN = `$HTTP${$REQ.$VALUE[...]}...`
...
- metavariable-regex:
metavariable: $HTTP
regex: ^(https?:\/\/|//)$
- pattern-either:
- pattern: $REQUEST.$METHOD($ASSIGN,...)
- pattern: $REQUEST.$METHOD($ASSIGN + $...FOO,...)
- pattern: $REQUEST.$METHOD(`${$ASSIGN}...`,...)
- patterns:
- pattern-either:
- pattern: $REQUEST.$METHOD("$HTTP"+$ASSIGN,...)
- pattern: $REQUEST.$METHOD("$HTTP"+$ASSIGN + $...A,...)
- pattern: $REQUEST.$METHOD(`$HTTP${$ASSIGN}...`,...)
- metavariable-regex:
metavariable: $HTTP
regex: ^(https?:\/\/|//)$
- pattern: $ASSIGN
- metavariable-regex:
metavariable: $METHOD
regex: ^(get|post|put|patch|del|head|delete)$
Examples
express-ssrf.ts
import { Request, Response, NextFunction } from 'express'
const request = require('request')
module.exports = function badNormal () {
return (req: Request, res: Response, next: NextFunction) => {
const url = "//"+req.body.imageUrl
const url1 = req.body['imageUrl'] + 123
// ruleid: express-ssrf
request.get(url)
// ruleid: express-ssrf
request.get(url1+123)
// ok: express-ssrf
request.get(`https://reddit.com/${req.query.url}/fooo`)
// ok: express-ssrf
request.get("https://google.com/"+req.query.url)
// ok: express-ssrf
request.get(config_value.foo+req.query.url)
// ok: express-ssrf
request.get(config_value.foo+req.body.shouldalsonotcatch)
// ok: express-ssrf
request.get(config_value.foo+req)
// ruleid: express-ssrf
request.get(req.body.url)
// ruleid: express-ssrf
request.get(`${req.query.url}/fooo`)
// ruleid: express-ssrf
request.get("//"+req.query.url+config_value.url)
const a = req.body.url
// ruleid: express-ssrf
request.get(a)
// ruleid: express-ssrf
request.get(`${url1}/fooo`)
// ruleid: express-ssrf
request.get(a+config_value.url)
// ok: express-ssrf
request.get(c+a)
// ok: express-ssrf
request.get(`${c}${a}/fooo`)
// ok: express-ssrf
request.get(c+a+config_value.url)
// ok: express-ssrf
request.get(c)
// ok: express-ssrf
request.get(`${c}`)
// ok: express-ssrf
request.get(c+config_value.url)
// ruleid: express-ssrf
request.get(req.body['url'])
// ruleid: express-ssrf
request.get(`${req.body['url']}/fooo`)
// ruleid: express-ssrf
request.get(req.body['url']+config_value.url)
// ruleid: express-ssrf
request.get("https://"+url1)
// ruleid: express-ssrf
request.get(`https://${req.body['url']}/fooo`)
// ruleid: express-ssrf
request.get("https://"+req.body['url']+config_value.url)
// ruleid: express-ssrf
request.get("//"+req.body['url']+config_value.url)
// ok: express-ssrf
request.get("//"+c+req.body['url']+config_value.url)
// todo: express-ssrf
request.get("https://google.com"+req.query.url)
}
}
module.exports = function badWithTypes () {
return ({ body }: Request, res: Response, next: NextFunction) => {
const url = body.url
// ruleid: express-ssrf
request.get(url)
}
}
module.exports = function goodWithTypes () {
return ({ params, query, session }: Request, res: Response, next: NextFunction) => {
const url = session
// ok: express-ssrf
request.get(url)
}
}
module.exports = function advanced () {
return ({ body }: Request, res: Response, next: NextFunction) => {
const url = body.url
joinModeOrDeepSemgrep(url, res, next)
}
function joinModeOrDeepSemgrep (url: string, res: Response, next: NextFunction) {
// todo: express-ssrf
request.get(url)
}
}
Short Link: https://sg.run/0PNw