javascript.express.security.audit.express-res-sendfile.express-res-sendfile

profile photo of semgrepsemgrep
Author
unknown
Download Count*

The application processes user-input, this is passed to res.sendFile which can allow an attacker to arbitrarily read files on the system through path traversal. It is recommended to perform input validation in addition to canonicalizing the path. This allows you to validate the path against the intended directory it should be accessing.

Run Locally

Run in CI

Defintion

rules:
  - id: express-res-sendfile
    message: The application processes user-input, this is passed to res.sendFile
      which can allow an attacker to arbitrarily read files on the system
      through path traversal. It is recommended to perform input validation in
      addition to canonicalizing the path. This allows you to validate the path
      against the intended directory it should be accessing.
    metadata:
      references:
        - https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
      technology:
        - express
      category: security
      cwe:
        - "CWE-73: External Control of File Name or Path"
      owasp:
        - A04:2021 - Insecure Design
      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) => {...}
          - pattern-either:
              - pattern: params
              - pattern: query
              - pattern: cookies
              - pattern: headers
              - pattern: body
      - patterns:
          - pattern-either:
              - patterns:
                  - pattern-either:
                      - pattern-inside: |
                          function ... (...,$REQ: $TYPE, ...) {...}
                  - metavariable-regex:
                      metavariable: $TYPE
                      regex: ^(string|String)
    pattern-sinks:
      - patterns:
          - pattern-either:
              - pattern: $RES.$METH($QUERY,...)
          - pattern-not-inside: $RES.$METH($QUERY,$OPTIONS)
          - metavariable-regex:
              metavariable: $METH
              regex: ^(sendfile|sendFile)$
          - focus-metavariable: $QUERY

Examples

express-res-sendfile.ts

import path = require('path')
import { Request, Response, NextFunction } from 'express'


module.exports = function badNormal () {
  return (req: Request, res: Response, next: NextFunction) => {
    const file = req.params.file
    // ruleid: express-res-sendfile
    res.sendFile(path.resolve('ftp/', file))
    // ruleid: express-res-sendfile
    res.sendFile(path.join('/ftp/', file))
    // ruleid: express-res-sendfile
    res.sendFile(file)
  }


}
module.exports = function goodNormal () {
  return (req: Request, res: Response, next: NextFunction) => {
    const file = 'foo'
    // ok: express-res-sendfile
    res.sendFile(path.resolve('ftp/', file))
    // ok: express-res-sendfile
    res.sendfile(req.app.get('staticFilePath') + '/index-test.html');
    // diffrent rule 
    // ok: express-res-sendfile
    res.sendfile(req.params.foo, {root: '/'});
    // ok: express-res-sendfile
    res.sendfile(req.params.foo, options);
  }

}


module.exports = function badWithTypes () {
  return ({ params, query }: Request, res: Response, next: NextFunction) => {
    const file = params.file
    // ruleid: express-res-sendfile
    res.sendFile(path.resolve('ftp/', file))
    // ruleid: express-res-sendfile
    res.sendFile(path.join('/ftp/', file))
    // ruleid: express-res-sendfile
    res.sendFile(file)
    // diffrent rule 
    // ok: express-res-sendfile
    res.sendfile(file, {root: '/'});
  }

}

module.exports = function goodWithTypes () {
  return ({ params, query, session }: Request, res: Response, next: NextFunction) => {
    const file = session
    // ok: express-res-sendfile
    res.sendFile(path.resolve('ftp/', file))
  }

}


module.exports = function advanced () {
  return ({ params, query }: Request, res: Response, next: NextFunction) => {
    const file = params.file

    if (!file.includes('/')) {
      joinModeOrDeepSemgrep(file, res, next)
    } 
  }

  function joinModeOrDeepSemgrep (file: string, res: Response, next: NextFunction) {

      // ruleid: express-res-sendfile
      res.sendFile(path.resolve('ftp/', file))

  }

}