javascript.express.security.injection.raw-html-format.raw-html-format

profile photo of semgrepsemgrep
Author
unknown
Download Count*

User data flows into the host portion of this manually-constructed HTML. This can introduce a Cross-Site-Scripting (XSS) vulnerability if this comes from user-provided input. Consider using a sanitization library such as DOMPurify to sanitize the HTML within.

Run Locally

Run in CI

Defintion

rules:
  - id: raw-html-format
    message: User data flows into the host portion of this manually-constructed
      HTML. This can introduce a Cross-Site-Scripting (XSS) vulnerability if
      this comes from user-provided input. Consider using a sanitization library
      such as DOMPurify to sanitize the HTML within.
    metadata:
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      references:
        - https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
      category: security
      technology:
        - express
      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:
        - Cross-Site-Scripting (XSS)
    languages:
      - javascript
      - typescript
    severity: WARNING
    mode: taint
    pattern-sources:
      - label: EXPRESS
        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
      - label: EXPRESSTS
        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
      - label: CLEAN
        by-side-effect: true
        patterns:
          - pattern-either:
              - pattern: $A($SOURCE)
              - pattern: $SANITIZE. ... .$A($SOURCE)
              - pattern: $A. ... .$SANITIZE($SOURCE)
          - focus-metavariable: $SOURCE
          - metavariable-regex:
              metavariable: $A
              regex: (?i)(.*valid|.*sanitiz)
    pattern-sinks:
      - requires: (EXPRESS and not CLEAN) or (EXPRESSTS and not CLEAN)
        patterns:
          - pattern-either:
              - patterns:
                  - pattern-either:
                      - pattern: '"$HTMLSTR" + $EXPR'
                      - pattern: '"$HTMLSTR".concat(...)'
                      - pattern: util.format($HTMLSTR, ...)
                  - metavariable-pattern:
                      metavariable: $HTMLSTR
                      language: generic
                      pattern: <$TAG ...
              - patterns:
                  - pattern: |
                      `...`
                  - pattern-regex: |
                      .*<\w+.*

Examples

raw-html-format.js

const express = require('express')
const app = express()
const port = 3000

app.get('/test', async (req, res) => {
    // ruleid: raw-html-format
    res.send("<h1>" + "message: " + req.query.message + "</h1>");
})

app.post('/test2', async (req, res) => {
    // ruleid: raw-html-format
    res.send(`<h1>message: ${req.query.message}</h1>`);
})

app.post('/test3', async (req, res) => {
    // ruleid: raw-html-format
    var html = "<h1>" + "message: " + req.query.message + "</h1>"
    res.send(html);
})

app.post('/test4', async (req, res) => {
    var html = "<h1> message"
    // ruleid: raw-html-format
    html = html.concat(req.query.message)
    html = html.concat("</h1>")
    res.send(html);
})

app.post('/ok-test', async (req, res) => {
    let { foobar } = req.query
    let sanitizedParam = sanitizeUrl(foobar)
    const url = `${baseUrl}/foo/bar?yo=123&param=${sanitizedParam}`
    // ok: raw-html-format
    return res.send(`<a href="${url}" />`)
})

app.get('/ok', async (req, res) => {
    // ok: raw-html-format
    res.send("message: " + req.query.message);
})

app.post('/ok2', async (req, res) => {
    // ok: raw-html-format
    res.send(`message: ${req.query.message}`);
})

app.post('/ok3', async (req, res) => {
    // ok: raw-html-format
    var data = "message: " + req.query.message;
    res.send(data);
})

app.post('/ok4', async (req, res) => {
    var data = "message: "
    // ok: raw-html-format
    data = data.concat(req.query.message)
    res.send(data);
})


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