contrib.nodejsscan.ssrf_puppeteer.puppeteer_ssrf

profile photo of returntocorpreturntocorp
Author
99
Download Count*
License

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: puppeteer_ssrf
    patterns:
      - pattern-inside: |
          require('puppeteer');
          ...
      - pattern-either:
          - pattern-inside: function $FUNC($REQ, $RES, ...) {...}
          - pattern-inside: $X = function $FUNC($REQ, $RES, ...) {...}
          - pattern-inside: var $X = function $FUNC($REQ, $RES, ...) {...};
          - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, ...) {...})
      - pattern-either:
          - pattern: $PAGE.goto(<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.goto(<... $REQ.$BODY ...>,...)
          - pattern: $PAGE.setContent(<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.setContent(<... $REQ.$BODY ...>,...)
          - pattern: $PAGE.evaluate(<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.evaluate(<... $REQ.$BODY ...>,...)
          - pattern: $PAGE.evaluateHandle(<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.evaluateHandle(<... $REQ.$BODY ...>,...)
          - pattern: $PAGE.evaluateOnNewDocument(<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.evaluateOnNewDocument(<... $REQ.$BODY ...>,...)
          - pattern: $PAGE.evaluate($CODE,<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.evaluate($CODE,<... $REQ.$BODY ...>,...)
          - pattern: $PAGE.evaluateHandle($CODE,<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.evaluateHandle($CODE,<... $REQ.$BODY ...>,...)
          - pattern: $PAGE.evaluateOnNewDocument($CODE,<... $REQ.$QUERY.$FOO ...>,...)
          - pattern: $PAGE.evaluateOnNewDocument($CODE,<... $REQ.$BODY ...>,...)
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.goto(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.goto(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.setContent(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.setContent(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.evaluate(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.evaluate(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.evaluateHandle(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.evaluateHandle(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.evaluateOnNewDocument(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.evaluateOnNewDocument(<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.evaluate($CODE,<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.evaluate($CODE,<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.evaluateHandle($CODE,<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.evaluateHandle($CODE,<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$QUERY.$FOO ...>;
              ...
              $PAGE.evaluateOnNewDocument($CODE,<... $INPUT ...>,...);
          - pattern: |
              $INPUT = <... $REQ.$BODY ...>;
              ...
              $PAGE.evaluateOnNewDocument($CODE,<... $INPUT ...>,...);
    message: If unverified user data can reach the `puppeteer` methods it can result
      in Server-Side Request Forgery vulnerabilities.
    metadata:
      owasp: A01:2017 - Injection
      cwe: "CWE-918: Server-Side Request Forgery (SSRF)"
      category: security
      technology:
        - node.js
        - express
        - puppeteer
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
    severity: ERROR
    languages:
      - javascript

Examples

ssrf_puppeteer.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()
    // ruleid: puppeteer_ssrf
    const url = `https://${req.query.name}`
    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: puppeteer_ssrf
    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();
    // ruleid: puppeteer_ssrf
    const body = req.body.foo;
    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: puppeteer_ssrf
    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();
    // ruleid: puppeteer_ssrf
    const body = req.body.foo;
    await page.evaluate('alert(' + body + ')');

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

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

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