javascript.express.security.express-expat-xxe.express-expat-xxe

profile photo of returntocorpreturntocorp
Author
2,175
Download Count*

Make sure that unverified user data can not reach the XML Parser, as it can result in XML External or Internal Entity (XXE) Processing vulnerabilities.

Run Locally

Run in CI

Defintion

rules:
  - id: express-expat-xxe
    message: Make sure that unverified user data can not reach the XML Parser, as it
      can result in XML External or Internal Entity (XXE) Processing
      vulnerabilities.
    metadata:
      deepsemgrep: true
      owasp:
        - A04:2017 - XML External Entities (XXE)
        - A05:2021 - Security Misconfiguration
      cwe:
        - "CWE-611: Improper Restriction of XML External Entity Reference"
      asvs:
        section: V5 Validation, Sanitization and Encoding
        control_id: 5.5.2 Insecue XML Deserialization
        control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v55-deserialization-prevention
        version: "4"
      references:
        - https://github.com/astro/node-expat
      category: security
      technology:
        - express
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - vuln
      likelihood: MEDIUM
      impact: MEDIUM
      confidence: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
    languages:
      - javascript
      - typescript
    severity: ERROR
    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-either:
              - pattern-inside: |
                  $XML = require('node-expat')
                  ...
              - pattern-inside: |
                  import $XML from 'node-expat'
                  ...
              - pattern-inside: |
                  import * as $XML from 'node-expat'
                  ...
          - pattern-either:
              - pattern-inside: |
                  $PARSER = new $XML.Parser(...);
                  ...
          - pattern-either:
              - pattern-inside: $PARSER.parse($QUERY)
              - pattern: $PARSER.write($QUERY)
          - pattern: $QUERY

Examples

express-expat-xxe.js

const express = require('express')
const app = express()
const port = 3000
const expat = require('node-expat');

app.get('/test', async (req, res) => {
    var parser = new expat.Parser('UTF-8')
    // ruleid: express-expat-xxe
    parser.parse(req.body)
    res.send('Hello World!')
})

app.get('/test1', async (req, res) => {
    var parser = new expat.Parser('UTF-8')
    // ruleid: express-expat-xxe
    parser.write(req.query.value)
    res.send('Hello World!')
})

app.get('/test2', async (req, res) => {
    var parser = new expat.Parser('UTF-8')
    var data = req.body.foo
    // ruleid: express-expat-xxe
    parser.write(data)
    res.send('Hello World!')
})

const test3 = function func3(req, res) {
    var parser = new expat.Parser('UTF-8')
    // ruleid: express-expat-xxe
    parser.parse(req.body)
    res.send('Hello World!')
}

const test4 = function (req, res) {
    var parser = new expat.Parser('UTF-8')
    // ruleid: express-expat-xxe
    parser.parse(req.body)
    res.send('Hello World!')
}

app.get('/okTest1', async (req, res) => {
    var parser = new expat.Parser('UTF-8')
    // ok: express-expat-xxe
    parser.write('<xml>hardcoded</xml>')
    res.send('Hello World!')
})

app.get('/okTest2', async (req, res) => {
    var parser = new expat.Parser('UTF-8')
    var data = foo()
    // ok: express-expat-xxe
    parser.write(data)
    res.send('Hello World!')
})

app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))