javascript.express.security.audit.express-path-join-resolve-traversal.express-path-join-resolve-traversal

Author
3,077
Download Count*
License
Possible writing outside of the destination, make sure that the target path is nested in the intended destination
Run Locally
Run in CI
Defintion
rules:
- id: express-path-join-resolve-traversal
message: Possible writing outside of the destination, make sure that the target
path is nested in the intended destination
metadata:
owasp:
- A05:2017 - Broken Access Control
- A01:2021 - Broken Access Control
cwe:
- "CWE-22: Improper Limitation of a Pathname to a Restricted Directory
('Path Traversal')"
category: security
references:
- https://owasp.org/www-community/attacks/Path_Traversal
technology:
- express
- node.js
cwe2022-top25: true
cwe2021-top25: true
subcategory:
- vuln
likelihood: HIGH
impact: MEDIUM
confidence: MEDIUM
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
languages:
- javascript
- typescript
severity: WARNING
mode: taint
pattern-sources:
- patterns:
- pattern-either:
- pattern-inside: function ... ($REQ, $RES) {...}
- pattern-inside: function ... ($REQ, $RES, $NEXT) {...}
- patterns:
- pattern-either:
- pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...})
- pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, $NEXT) {...})
- metavariable-regex:
metavariable: $METHOD
regex: ^(get|post|put|head|delete|options)$
- pattern-either:
- pattern: $REQ.query
- pattern: $REQ.body
- pattern: $REQ.params
- pattern: $REQ.cookies
- pattern: $REQ.headers
- patterns:
- pattern-either:
- pattern-inside: |
({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) =>
{...}
- pattern-inside: |
({ $REQ }: Request,$RES: Response) => {...}
- focus-metavariable: $REQ
- pattern-either:
- pattern: params
- pattern: query
- pattern: cookies
- pattern: headers
- pattern: body
pattern-sinks:
- patterns:
- pattern: $SINK
- pattern-either:
- pattern-inside: |
$PATH = require('path');
...
- pattern-inside: |
import $PATH from 'path';
...
- pattern-either:
- pattern-inside: $PATH.join(...,$SINK,...)
- pattern-inside: $PATH.resolve(...,$SINK,...)
- patterns:
- pattern: $SINK
- pattern-inside: |
import 'path';
...
- pattern-either:
- pattern-inside: path.join(...,$SINK,...)
- pattern-inside: path.resolve(...,$SINK,...)
pattern-sanitizers:
- pattern: $Y.replace(...)
- pattern: $Y.indexOf(...)
- pattern: |
function ... (...) {
...
<... $Y.indexOf(...) ...>
...
}
- patterns:
- pattern: $FUNC(...)
- metavariable-regex:
metavariable: $FUNC
regex: sanitize
Examples
express-path-join-resolve-traversal.js
const path = require('path')
const express = require('express')
const app = express()
const port = 3000
app.get('/test1', (req, res) => {
// ruleid:express-path-join-resolve-traversal
var extractPath = path.join(opts.path, req.query.path);
extractFile(extractPath);
res.send('Hello World!');
})
app.post('/test2', function test2(req, res) {
// ruleid:express-path-join-resolve-traversal
createFile({filePath: path.resolve(opts.path, req.body)})
res.send('Hello World!')
})
function testCtrl3(req,res) {
let somePath = req.body.path;
// ruleid:express-path-join-resolve-traversal
const pth = path.join(opts.path, somePath);
extractFile(pth);
res.send('Hello World!');
}
const func4 = function testCtrl4(req,res) {
let somePath = req.body.path;
// ruleid:express-path-join-resolve-traversal
const pth = path.join(opts.path, somePath);
extractFile(pth);
res.send('Hello World!');
}
const func5 = function (req,res) {
let somePath = req.body.path;
// ruleid:express-path-join-resolve-traversal
const pth = path.join(opts.path, somePath);
extractFile(pth);
res.send('Hello World!');
}
app.post('/test3', testCtrl3)
app.post('/test5', function (req,res) {
let data = req.body.path;
for (let i = 0; i < data.length; i++) {
// ruleid:express-path-join-resolve-traversal
var pth = path.join(opts.path, data[i]);
doSmth(pth);
}
})
app.post('/ok-test1', function okTest1(req,res) {
let data = ['one', 'two', 'three'];
for (let x of data) {
// ok:express-path-join-resolve-traversal
var pth = path.join(opts.path, x);
doSmth(pth);
}
})
app.post('/ok-test2', function okTest2() {
function someFunc() {
createFile({
// ok:express-path-join-resolve-traversal
filePath: path.join(__dirname, 'val')
})
return true
}
someFunc()
})
app.post('/ok-test3', function (req,res) {
let somePath = req.body.path;
somePath = somePath.replace(/^(\.\.(\/|\\|$))+/, '');
// ok:express-path-join-resolve-traversal
return path.join(opts.path, somePath);
})
app.post('/ok-test4', function (req,res) {
let somePath = sanitizer(req.body.path);
// ok:express-path-join-resolve-traversal
return path.join(opts.path, somePath);
})
app.post('/ok-test5', function okTest5(req,res) {
let somePath = req.body.path;
// ok:express-path-join-resolve-traversal
let result = path.join(opts.path, somePath);
if (result.indexOf(opts.path) === 0) {
return path;
}
return null
})
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
Short Link: https://sg.run/weRn