javascript.express.security.express-puppeteer-injection.express-puppeteer-injection

profile photo of returntocorpreturntocorp
Author
3,077
Download Count*

If unverified user data can reach the puppeteer methods it can result in Server-Side Request Forgery vulnerabilities

Run Locally

Run in CI

Defintion

rules:
  - id: express-puppeteer-injection
    message: If unverified user data can reach the `puppeteer` methods it can result
      in Server-Side Request Forgery vulnerabilities
    metadata:
      owasp:
        - A10:2021 - Server-Side Request Forgery (SSRF)
      cwe:
        - "CWE-918: Server-Side Request Forgery (SSRF)"
      category: security
      technology:
        - express
      references:
        - https://pptr.dev/api/puppeteer.page
      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: |
                  require('puppeteer');
                  ...
              - pattern-inside: |
                  import 'puppeteer';
                  ...
          - pattern-either:
              - pattern: $PAGE.goto($SINK,...)
              - pattern: $PAGE.setContent($SINK,...)
              - pattern: $PAGE.evaluate($SINK,...)
              - pattern: $PAGE.evaluate($CODE,$SINK,...)
              - pattern: $PAGE.evaluateHandle($SINK,...)
              - pattern: $PAGE.evaluateHandle($CODE,$SINK,...)
              - pattern: $PAGE.evaluateOnNewDocument($SINK,...)
              - pattern: $PAGE.evaluateOnNewDocument($CODE,$SINK,...)
          - focus-metavariable: $SINK

Examples

express-puppeteer-injection.js

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

app.get('/', async (req, res) => {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()
    const url = `https://${req.query.name}`
    // ruleid: express-puppeteer-injection
    await page.goto(url)

    await page.screenshot({path: 'example.png'})
    await browser.close()

    res.send('Hello World!')
})

app.post('/test', async (req, res) => {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()
    // ruleid: express-puppeteer-injection
    await page.setContent(`${req.body.foo}`)

    await page.screenshot({path: 'example.png'})
    await browser.close()

    res.send('Hello World!')
})

const controller = async (req, res) => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    const body = req.body.foo;
    // ruleid: express-puppeteer-injection
    await page.setContent('<html>' + body + '</html>');

    await page.screenshot({path: 'example.png'});
    await browser.close();

    res.send('Hello World!');
}

app.post('/test2', async (req, res) => {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()
    // ruleid: express-puppeteer-injection
    await page.evaluateOnNewDocument(`${req.body.foo}`)

    await page.screenshot({path: 'example.png'})
    await browser.close()

    res.send('Hello World!')
})

const controller2 = async (req, res) => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    const body = req.body.foo;
    // ruleid: express-puppeteer-injection
    await page.evaluate('alert(' + body + ')');

    await page.screenshot({path: 'example.png'});
    await browser.close();

    res.send('Hello World!');
}

app.post('/test2', controller)

app.post('/ok-test', async (req, res) => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    // ok: express-puppeteer-injection
    await page.goto('https://example.com');

    await page.screenshot({path: 'example.png'});
    await browser.close();

    res.send('Hello World!');
})

const controller = async (req, res) => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    // ok: express-puppeteer-injection
    const body = '<div>123</div>';
    await page.setContent('<html>' + body + '</html>');

    await page.screenshot({path: 'example.png'});
    await browser.close();

    res.send('Hello World!');
}

app.post('/ok-test2', controller)

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