contrib.nodejsscan.ssrf_node.node_ssrf

profile photo of returntocorpreturntocorp
Author
99
Download Count*
License

User controlled URL in http client libraries can result in Server Side Request Forgery (SSRF).

Run Locally

Run in CI

Defintion

rules:
  - id: node_ssrf
    patterns:
      - pattern-either:
          - pattern-inside: |
              require('request');
              ...
          - pattern-inside: |
              require('axios');
              ...
          - pattern-inside: |
              require('needle');
              ...
          - pattern-inside: |
              require('bent');
              ...
          - pattern-inside: |
              require('urllib');
              ...
          - pattern-inside: |
              require('net');
              ...
          - pattern-inside: |
              require('https');
              ...
          - pattern-inside: |
              require('superagent');
              ...
          - pattern-inside: |
              require('got');
              ...
      - 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: |
              $PKG.get(<... $REQ.$VAR ...>, ...)
          - pattern: |
              $PKG.get(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              $PKG.post(<... $REQ.$VAR ...>, ...)
          - pattern: |
              $PKG.post(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              $PKG.put(<... $REQ.$VAR ...>, ...)
          - pattern: |
              $PKG.put(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: >
              needle("=~/[get|post|put|GET|POST|PUT]+/", <... $REQ.$VAR.$FOO
              ...>, ...)
          - pattern: >
              needle("=~/[get|post|put|GET|POST|PUT]+/", <... $REQ.$VAR ...>,
              ...)
          - pattern: |
              request(<... $REQ.$VAR ...>, ...)
          - pattern: |
              request(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              $PKG.request(<... $REQ.$VAR ...>, ...)
          - pattern: |
              $PKG.request(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              getJSON(<... $REQ.$VAR ...>, ...)
          - pattern: |
              getJSON(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              getBuffer(<... $REQ.$VAR ...>, ...)
          - pattern: |
              getBuffer(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              fetch(<... $REQ.$VAR ...>, ...)
          - pattern: |
              fetch(<... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              $SOCKET.connect($PORT, <... $REQ.$VAR ...>, ...)
          - pattern: |
              $SOCKET.connect($PORT, <... $REQ.$VAR.$FOO ...>, ...)
          - pattern: |
              $PKG.get(..., {host: <... $REQ.$VAR ...>}, ...)
          - pattern: |
              $PKG.get(..., {host: <... $REQ.$VAR.$FOO ...>}, ...)
          - pattern: |
              $PKG.get(..., {hostname: <... $REQ.$VAR ...>}, ...)
          - pattern: |
              $PKG.get(..., {hostname: <... $REQ.$VAR.$FOO ...>}, ...)
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              $PKG.get(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              $PKG.get(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              $PKG.post(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              $PKG.post(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              $PKG.put(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              $PKG.put(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              needle("=~/[get|post|put|GET|POST|PUT]+/", <... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              needle("=~/[get|post|put|GET|POST|PUT]+/", <... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              request(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              request(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              $PKG.request(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              $PKG.request(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              getJSON(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              getJSON(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              getBuffer(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              getBuffer(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              fetch(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              fetch(<... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              $SOCKET.connect($PORT, <... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              $SOCKET.connect($PORT, <... $INP ...>, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              $PKG.get(..., {host: <... $INP ...>}, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              $PKG.get(..., {host: <... $INP ...>}, ...);
          - pattern: |
              $INP = <... $REQ.$VAR ...>;
              ...
              $PKG.get(..., {hostname: <... $INP ...>}, ...);
          - pattern: |
              $INP = <... $REQ.$VAR.$FOO ...>;
              ...
              $PKG.get(..., {hostname: <... $INP ...>}, ...);
    message: User controlled URL in http client libraries can result in Server Side
      Request Forgery (SSRF).
    languages:
      - javascript
    severity: ERROR
    metadata:
      owasp: A01:2017 - Injection
      cwe: "CWE-918: Server-Side Request Forgery (SSRF)"
      category: security
      technology:
        - node.js
        - express
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]

Examples

ssrf_node.js

var express = require('express');
const request = require('request');
var needle = require('needle');
var app = express();
const bent = require('bent')
const getJSON = bent('json')
const getBuffer = bent('buffer')
var urllib = require('urllib');
const http = require('http');
let axios = require('axios');
var http = require('https');

app.get('/', function (req, res) {
    // ruleid:node_ssrf
    request(req.query.name, function (error, response, body) {
        console.error('error:', error); // Print the error if one occurred
        console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
        console.log('body:', body); // Print the HTML for the Google homepage.
    });

    // ruleid:node_ssrf
    needle('get', req.vbody.foo).then(res => {
        console.log(res.body);
    })
        .catch(err => {
            console.error(err);
        });


    // ruleid:node_ssrf
    urllib.request(req.foo, function (err, data, res) {
        if (err) {
            throw err; // you need to handle error
        }
        console.log(res.statusCode);
        console.log(res.headers);
        // data is Buffer instance
        console.log(data.toString());
    });

});

app.get('/', function (req, res) {

    // ruleid:node_ssrf
    needle.get(req.foo, function (error, response) {
        if (!error && response.statusCode == 200)
            console.log(response.body);
    });


    // ruleid:node_ssrf
    needle.post(req.foo)
        .pipe(out)
        .on('finish', () => {
            console.log('pipe done');
        });

    //Do not match this
    needle.get('http://google.com')
        .pipe(out)
        .on('finish', () => {
            console.log('pipe done');
        });

    //Do not match this to reduce false positives
    needle.get(somvar)
        .pipe(out)
        .on('finish', () => {
            console.log('pipe done');
        });


    // ruleid:node_ssrf
    axios.get(req.foo.bar)
        .then(function (response) {
            // handle success
            console.log(response);
        })
        .catch(function (error) {
            // handle error
            console.log(error);
        })
        .finally(function () {
            // always executed
        });


    // ruleid:node_ssrf
    let obj = await getJSON(req.foo);
    // ruleid:node_ssrf
    let buffer = await getBuffer(req.foo);

    // ruleid:node_ssrf
    fetch(req.post.doo, { method: 'POST', body: 'a=1' })
        .then(res => res.json()) // expecting a json response
        .then(json => console.log(json));

});

app.listen(8000);

// do not match
needle.get(foo, function (error, response) {
    if (!error && response.statusCode == 200)
        console.log(response.body);
});


var net = require('net');


app.get('/', function (req, res) {
    var client = new net.Socket();
    // ruleid:node_ssrf
    client.connect(1337, req.body.host, function () {
        console.log('Connected');
        client.write('Hello, server! Love, Client.');
    });

    client.on('data', function (data) {
        console.log('Received: ' + data);
        client.destroy(); // kill client after server's response
    });

    client.on('close', function () {
        console.log('Connection closed');
    });




    // ruleid:node_ssrf
    const fk = http.get({ host: req.foo });
    req.end();
    req.once('response', (res) => {
        const ip = req.socket.localAddress;
        const port = req.socket.localPort;
        console.log(`Your IP address is ${ip} and your source port is ${port}.`);
        // Consume response object
    });

});