Working on the web has never been easier and more engaging than now. And for those who are privileged enough to have the skills and have put in the hours to develop themselves, the opportunities are out there.
Being an exceedingly productive developer keeps getting easier. But as our leverage grows, so do the threats and liabilities we need to mitigate. Thus, our expertise needs to grow and meet those challenges.
For that purpose, we have created a series of articles tackling the most common security threats and how to mitigate them effectively. These articles cover an extensive range of topics and technologies so that no matter what your stack of choice is, we have an article for you. This article will cover the topic of command injection in the context of Vue.js. We will use Node.js as our runtime and controller layer, including Vue.js as the view framework.
If you have no experience with any of these technologies, we recommend that you take a quick look at these guides to Node.js and Vue.js to familiarize yourself with them. We will not be going too deep into the specifics of these technologies, but a basic understanding of JavaScript and the concepts of these stacks is required. We promise it won't take too long. You will learn some cool stuff too. If, however, your expertise lies in another development stack, please read our other articles and find your flavor of choice.
Without further ado, let's get to it.
Introduction to Vue.js
Alright, so let's first briefly explore how to set up our Vue.js project. This will be the project we will be working on for this article.
Building a Node.js project with Vue.js is deceptively easy. First, let's create our Node.js app using express-generator. If you don't have the express-generator, you can install it with the following command:
npm install -g express-generator
Once installed, you can proceed to create our project app by running the following command:
express demo_vue_app --no-view
This command will generate a boilerplate app called demo_vue_app with everything you need and no view engine. If you want to use a specific view engine, you can use the -v flag to pick one.
Great! Now that we created the project, we need to install the dependencies required for running. Just run the following command while inside the project folder:
npm install
Finally, you can just run the project and see the website running with the following command:
npm start
This results in the following:
The result of the above code
Yay!
One last thing. To add Vue.js to our project, you need only add the following tag to the index.html file:
<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
And that's pretty much it, really.
Now we can start.
What Is Command Injection?
So, what is command injection?
Command injection, or code injection, is a particular kind of injection attack where an attacker sends JavaScript or Node.js code to the server in an attempt to seize control over it. The browser or the Node.js runtime then interprets this malicious code, and it can't distinguish between the code the developer intended and the code that the attacker provided as input.
The main reason this attack can escalate and jeopardize the server's stability is the ability of Node.js to run commands in the OS. Internally, Node.js contains a module called child_process. This allows JavaScript code to run programs, as well as communicate with them through standard I/O provided by the underlying operating system. In addition, users can often start such programs interactively using the command line interface.
Moreover, the ability to make use of the child_process module to run external processes dramatically enhances the power of the Node.js standard library.
Of course, this can be our Achilles' heel if we are not careful with our security and implementation.
Command Injection Example
Now that we know what command injection is, let's see an example.
Assuming that our victim application uses a function that uses the aforementioned child_process module—say, the exec Node.js function or the eval JavaScript function—then an attacker can reach our server command line by simply appending commands to the request.
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
// Get parameter. In this case, the image name.
const image = req.query.image;
// Execute the command
exec(`git log ${image}`, (err, output) => {
// Respond with HTTP 500 if there was an error
if (err) {
res.status(500).send(err);
return;
}
// output the HTTP response
res.send(output);
});
});
module.exports = router;
An excellent example of this is the following:
curl http://localhost:3000/index?image=prof.png;nc%20-l%205656%20|/bin/bash
In this request, you can see that the attacker is trying to run the following command:
nc -l 5656 | /bin/bash
This command, called netcat, can run arbitrary code on our server on behalf of the attacker and do significant damage.
Fixing Command Injection Vulnerabilities
So then, how do we go about preventing this from happening?
The first line of defense is incredibly effective against this kind of threat, and that is to do away with using functions that execute commands at a low level altogether.
Avoid eval(), exec(), setTimeout(), setInterval(), and any other function that allows dynamic code, unless absolutely necessary. Additionally, avoid new Function() for reasons that should be obvious at this point.
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
// Get parameter. In this case, the image name.
const image = req.query.image;
// Execute the command
execFile(`git log ${image}`, (err, output) => {
// Respond with HTTP 500 if there was an error
if (err) {
res.status(500).send(err);
return;
}
// output the HTTP response
res.send(output);
});
});
module.exports = router;
Once you have done that, make sure to employ some input sanitization mechanism for any user input that your application allows. As a general rule of thumb, it is essential to restrict and regulate all avenues of user input that our application provides. For example, look for ways to change dynamic input into fixed selection. And make sure to safelist all input to sensitive functionalities.
Other Flaws
Additionally, avoid using code serialization in JavaScript. Yes, it is a thing, and no, you shouldn't overlook it. Thankfully, chances are that you won’t need to code your own serialization and deserialization solution for your platform. However, in the vast library of npm exists more than 1,600,000 open-source packages ready to be used, and one of them might end up implementing a form of serialization. If we are not careful with what we bring to our platform, we might introduce unnecessary vulnerabilities to our code.
Finally, make use of security analysis tools like StackHawk and regularly scan your application for injection vulnerabilities. StackHawk tests your applications, services, and APIs for any security vulnerabilities that may have been introduced by your team, and it also checks for exploitable open-source security bugs. This means that application security can keep up with the fast pace that engineering teams of today have to maintain. Use StackHawk to find vulnerable spots at the pull request and fix them quickly, leaving outdated security tools in the dust as they wait for someone to start a scan manually.
Final Thoughts
Making attractive and robust applications for our clients is now a breeze with technologies like Node.js and Vue.js. Having extensive expertise and history on these development stacks is becoming more and more attractive every year.
Gone are the days when countless hours and thousands of dollars needed to be invested in building the know-how. Instead, producing a mid-size production-ready product is now an affair that a single individual could potentially carry out.
Nevertheless, it is essential to know that, despite the enormous leverage and versatility that these development stacks offer, it is still crucial to keep an eye on our security. Now that JavaScript is such a mature and robust language, there are many things that can be done to solve our vulnerability issues. Therefore, finding and choosing a simple and adequate solution is important without introducing unnecessary complexity.
This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.