Vulnerable Node.js Application

This application is a modified version of our original Hello World application. In this example we will purposely introduce a vulnerability and show how Intrinsic mitigates exploitation of said vulnerability.

Previously we had hardcoded a URL for making outbound requests. Now we will accept the URL from the point of invocation. Of course, this is absolutely insecure and represents an extreme security risk. Production code should never rely on such a feature.

Code for server.js

const http = require('request');
const express = require('express');
const bodyParser = require('body-parser');

const credentials = require('./credentials.json');
const app = express();
app.use(bodyParser.json());
const PORT = process.env.NODE_PORT;

app.get('/example', (req, res) => {
  // Warning: Dangerous Anti-Pattern
  request.get(`${req.body.url}?auth=${credentials}`, (err, data) => {
    if (err) return res.status(500).send(err.message);

    res.status(200).json({
      body: data.body
    });
  });
});

app.listen(PORT, () => {
  console.log(`Example app listening on port ${PORT}!`);
});

In this situation we are trusting the data being provided to us when the request is received is invoked. We assume that the destination URL will always be a safe one. Unfortunately if an attacker were to make a request to our Node.js application they could then steal our sensitive information (i.e., the credentials data) by sending it to a server they control.

This example is a little contrived, but you can imagine other scenarios where a path segment isn't sanitized properly and contains ../../etc/passwd, or if we are determining arguments for a file to pass to rm -rf and are provided / # normal-file.jpg by an attacker.

Code for intrinsic.js

The intrinsic.js file is pretty straightforward; it simply loads our policy file and runs the normal Node.js application code:

const intrinsic = require('@intrinsic/intrinsic');

intrinsic(__filename)
  .loadPolicies('./intrinsic-policy.js')
  .run('./server.js');

Code for intrinsic-policy.js

With the following configuration we are only allowing outbound requests to any URLs located under the trusted api.intrinsic.com domain:

'use strict';
const { HttpServer } = require('@intrinsic/intrinsic');
const server = new HttpServer({ port: process.env.NODE_PORT });

server.addSandbox('get', '/example', policy => {
    policy.outboundHttp.allowGet('https://api.intrinsic.com/**');
});

module.exports = server;

With Intrinsic running, any requests made which would normally send credentials to a foreign server would instead result in an error with the outbound request never being made.

Example Output

If we run our vulnerable web application and make an HTTP request with a url parameter of https://api.intrinsic.com/account, the request will succeed and the application will run as per usual. However, if an attacker were to make a request and specify a url parameter such as http://evil.example.com/, the request would fail and a violation would happen. When this happens you will see the following message in your applications stderr output:

[INTRINSIC] OutboundHttpPolicyViolation: POLICY_VIOLATION sb: "0"
  | [GET] http://evil.example.com/ not in outbound http whitelist