Shell injection is a form of injection attack where the target is the underlying operating system. More specifically, the attackers are focusing on the com-mands executed by the web application in the operating system layer. In Node.js this means commands executed through the child_process module, using exec, execFile, spawn, or fork. These commands can execute scripts on the oper-ating system and can become a possible attack vector for code injection if the commands are incorrectly constructed with user input.
As with interpreter functions, shell commands are useful because they sim-plify the application logic by pushing certain tasks to external libraries. The two differences are the character set used and the execution environment.
The attacker may not have access to runtime variables in a shell injection attack, but there are still plenty of ways to cause serious damage.
You might be thinking “Great. Another group of commands I simply won’t use.” As before, there are situations where shell commands drastically simplify the development or are required because of parallelism needs, for example, when we write an application that provides IP address information about URLs. We could look for a third-party module or write code to connect to the Domain Name System (DNS) and look up the information. Or we could use a command that comes with practically every operating system, host:
chp-4-code-injection/shell.js 'use strict';
var express = require('express');
var bodyParser = require('body-parser');
var exec = require('child_process').exec;
var app = express();
var form = '' +
'<form method="POST" action="/host">' +
'<input type="text" name="host" placeholder="host" />' + '<input type="submit" value="Get host" />' +
'</form>';
app.get('/', function(req, res){
res.send(form);
});
Avoid Shell Injection in Your Application
•
47app.use(bodyParser.urlencoded({extended: false}));
app.post('/host', function (req, res) {
exec('host ' + req.body.host, function (err, stdout, stderr) { if(err || stderr) {
console.error(err || stderr);
res.sendStatus(500);
return;
}
res.send(
'<h3>Lookup for: ' + req.body.host + '</h3>' + '<pre>' + stdout + '</pre>' +
form );
});
});
app.listen(3000);
And now a user could simply ask for information on Google.com, for example, and get a nice output, as shown in the figure on page 49.
Again, the problem is trusting the user to send valid input. What if the attacker sends something like google.com | cat /etc/shadow? If you don’t validate the user input, the attacker will probably be able to see the contents of the server’s /etc/shadow file, containing password information. Not a good result.
The first recommendation is to use execFile instead of exec when possible. The exec command uses the /bin/sh shell interpreter to run the command, which can be targeted by attackers to break out and launch other commands. execFile, however, executes the file directly, giving attackers a much smaller attack surface (limited by the file being executed). On the downside, you will lose some interoperability between environments and the ability to run complex commands with piping, but in turn your code will be more secure.
chp-4-code-injection/shell-fix-execfile.js
var execFile = require('child_process').execFile;
app.post('/host', function (req, res) {
execFile('/usr/bin/host', [req.body.host], function (err, stdout, stderr) { // . . .
});
});
Another way to mitigate the attack surface is to whitelist and typecast the user-supplied variables when constructing shell commands. First, let’s whitelist our input and allow only certain characters:
Chapter 4. Avoid Code Injections
•
48chp-4-code-injection/shell-fix-whitelist.js
app.post('/host', function (req, res) { var host = req.body.host || '';
// Test for everything besides alphanumeric and . and // Also test for starting . and
-if(host.match(/^[-\.]|[^a-zA-Z0-9\-\.]/)) { res.status(400).send('Invalid input');
return;
}
execFile('/usr/bin/host', [host], function (err, stdout, stderr) { // ...
});
});
This fix is effective because it prevents users from creating other commands or manipulating them in unseemly ways. But whitelisting isn’t always possible, Avoid Shell Injection in Your Application
•
49so instead we can limit run rights. We limit the rights the Node.js process has when executing the command by running it as a user with a limited set of rights. We can do this by providing corresponding uid or gid options.
Most Unix systems have a nobody user that we can use to run common services.
We can set up our code to run the command as nobody by looking for the UID and setting it in the command options:
chp-4-code-injection/shell-fix-uid.js var opts = {};
app.post('/host', function (req, res) {
// Add options specifying uid, which we asked from system execFile('/usr/bin/host', [req.body.host],
opts, function (err, stdout, stderr) { if(err || stderr) {
'<h3>Lookup for: ' + req.body.host + '</h3>' + '<pre>' + stdout + '</pre>' +
form );
});
});
// Look for the nobody user
// NOTE:
// On OSX this can cause an error because the UID of nobody // is a negative number (-1) represented by overflowing integer
execFile('/usr/bin/id', ['-u', 'nobody'], function (err, stdout, stderr) { if(err || stderr) {
console.error(err || stderr);
process.exit(1);
}
// Set the uid in the options opts.uid = +stdout;
// Start server
console.log('Nobody is ' + opts.uid);
console.log('Listening on 3000');
app.listen(3000);
});
Now, an attacker trying to see the /etc/shadow file will see an error because it’s a restricted file and the nobody user doesn’t have access to it.
Chapter 4. Avoid Code Injections
•
50For best results, use the combination of all mitigation methods: execFile, limit access rights and possible inputs. As you can see, the defense methods for both shell injection and code injection follow the same principles.
Wrapping Up
In this chapter we looked at one of the most versatile and popular attack vectors in the enemy’s arsenal—code injection. You should now know how to identify possible attack locations and how to properly validate user input.
You also learned about minimizing possible damages by limiting access rights of your processes.
In the next chapter, we’ll dig deeper into this attack vector and learn how it targets the database and what we can do to keep it safe.
Wrapping Up
•
51CHAPTER 5
Real knowledge is to know the extent of one’s ignorance.
➤ Confucius