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

profile photo of semgrepsemgrep
Author
3,077
Download Count*

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]
      vulnerability_class:
        - Path Traversal
    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:
          - focus-metavariable: $SINK
          - pattern-either:
              - pattern-inside: |
                  $PATH = require('path');
                  ...
              - pattern-inside: |
                  import $PATH from 'path';
                  ...
          - pattern-either:
              - pattern: $PATH.join(...,$SINK,...)
              - pattern: $PATH.resolve(...,$SINK,...)
      - patterns:
          - focus-metavariable: $SINK
          - pattern-inside: |
              import 'path';
              ...
          - pattern-either:
              - pattern: path.join(...,$SINK,...)
              - pattern: 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}`))