contrib.nodejsscan.eval_vm2_injection.vm2_code_injection

profile photo of returntocorpreturntocorp
Author
99
Download Count*
License

Untrusted user input reaching vm2 can result in code injection.

Run Locally

Run in CI

Defintion

rules:
  - id: vm2_code_injection
    patterns:
      - pattern-inside: |
          require('vm2');
          ...
      - pattern-either:
          - pattern-inside: function ($REQ, $RES, ...) {...}
          - 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: |
              $VM.run(<... $REQ.$QUERY.$FOO ...>,...);
          - pattern: |
              $CODE = <... $REQ.$QUERY.$FOO ...>;
              ...
              $VM.run(<... $CODE ...>,...);
          - pattern: |
              new VM(...).run(<... $REQ.$QUERY.$FOO ...>,...);
          - pattern: |
              new NodeVM(...).run(<... $REQ.$QUERY.$FOO ...>,...);
          - pattern: |
              $CODE = <... $REQ.$QUERY.$FOO ...>;
              ...
              new NodeVM(...).run(<... $CODE ...>,...);
          - pattern: |
              $CODE = <... $REQ.$QUERY.$FOO ...>;
              ...
              new VMScript(<... $CODE ...>,...);
          - pattern: |
              $VM.run(<... $REQ.$BODY ...>,...);
          - pattern: |
              $CODE = <... $REQ.$BODY ...>;
              ...
              $VM.run(<... $CODE ...>,...);
          - pattern: |
              new VM(...).run(<... $REQ.$BODY ...>,...);
          - pattern: |
              $CODE = <... $REQ.$BODY ...>;
              ...
              new VM(...).run($CODE,...);
          - pattern: |
              new NodeVM(...).run(<... $REQ.$BODY ...>,...);
          - pattern: |
              $CODE = <... $REQ.$BODY ...>;
              ...
              new NodeVM(...).run(<... $CODE ...>,...);
          - pattern: |
              $CODE = <... $REQ.$BODY ...>;
              ...
              new VMScript(<... $CODE ...>,...);
    message: Untrusted user input reaching `vm2` can result in code injection.
    severity: WARNING
    languages:
      - javascript
    metadata:
      owasp: A01:2017 - Injection
      cwe: "CWE-94: Improper Control of Generation of Code ('Code Injection')"
      category: security
      technology:
        - node.js
        - express
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]

Examples

eval_vm2_injection.js

const fs = require('fs');
const { VM, NodeVM } = require('vm2');
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.get('/test1', (req, res) => {
    // ruleid:vm2_code_injection
    code = `
    console.log(${req.query.input})
  `;

    const sandbox = {
        setTimeout,
        fs: {
            watch: fs.watch
        }
    };

    new VM({
        timeout: 40 * 1000,
        sandbox
    }).run(code);

    res.send('hello world');
})

app.get('/test2', function (req, res) {
    const sandbox = {
        setTimeout,
        fs: {
            watch: fs.watch
        }
    };


    const nodeVM = new NodeVM({ timeout: 40 * 1000, sandbox });
    // ruleid:vm2_code_injection
    nodeVM.run('console.log(' + req.query.input + ')')

    res.send('hello world');
})

app.get('/test3', function (req, res) {
    const sandbox = {
        setTimeout,
        fs: {
            watch: fs.watch
        }
    };

    const nodeVM = new NodeVM({ timeout: 40 * 1000, sandbox });
    // ruleid:vm2_code_injection
    const script = new VMScript(`console.log(${req.query.input})`)
    nodeVM.run(script)

    res.send('hello world')
})


app.get('/test4', async function test1(req, res) {
    code = `
    console.log("Hello world")
  `;

    // ruleid:vm2_context_injection
    const sandbox = {
        setTimeout,
        watch: req.query.input
    };

    return new VM({ timeout: 40 * 1000, sandbox }).run(code);
})

app.post('/test5', function test2(req, res) {
    // ruleid:vm2_context_injection
    const sandbox = {
        setTimeout,
        input: req.body
    };

    const nodeVM = new NodeVM({ timeout: 40 * 1000, sandbox });
    return nodeVM.run('console.log("Hello world")')
})


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