Are you under attack? Detect attacks against Node.js applications

Make attacks difficult for hackers


When hackers attempt to break into a web application, they usually first map it out by following every link to find all the valid paths. Then, they attempt to enter various invalid values in the input fields to see whether the application suffers from any of the well-known code injection vulnerabilities. In this tutorial, you learn how to detect these attacks. Once you detect them, you can shut down access from the attacking IP address, redirecting it into a slow "tar pit," or perform other actions to become a harder target.

What you'll need to build your application

  • A Bluemix® account.
  • Knowledge of HTML and JavaScript.
  • Knowledge of the MEAN application stack (at least Node.js and Express). (If you are not familiar with MEAN, you can get started with "Build a self-posting Facebook application with Bluemix and the MEAN stack," a three-part tutorial on IBM developerWorks.)
  • A development environment that can upload a Node.js application to Bluemix, such as Eclipse.

Run the appGet the code

In this tutorial, you learn how to detect when your IBM Bluemix Node.js web application is being scanned and attacked.

The demonstration application

This application contains one main page, which contains a login form. At the top of the form, there are buttons to make it easy to issue various attacks. Beneath those buttons are buttons to put dangerous values into a cookie. At the bottom of the page, there is a list of all attacks detected in the last hour. Note that the time reported by the application is UTC, not your local time.

Screen capture of application screen
Screen capture of application screen

The techniques that are demonstrated in this article detect four types of attacks:

  • Application mapping, which can include access to various "pages" that do not really exist.
  • Attacks that are encoded in the query string (the part of the URL after the question mark, which is where parameters of a GET form reside).
  • Attacks that are encoded in the post parameters (part of the HTTP header, which is where parameters of a POST form reside).
  • Attacks in cookie values.

You can issue any of those attacks as follows:

  • Application mapping
    Click Link to nowhere and you will be redirected to an invalid path (/badPath.html).
  • Query string attacks
    Click Attack in a query parameter. You will be redirected to /login, which is valid (try accessing it directly to verify), but with a dangerous query parameter.
  • Post parameter attacks
    Two post parameter attacks are available automatically: a SQL injection and a local file inclusion. Clicking these buttons does not cause the browser-side application to issue the attack by itself. Instead, it puts the attack value in the user name field. You then type a password (or not) and click Submit to submit the form with the attack. I adopted this two-step approach because with post parameters, in contrast to paths and query string parameters, you don't see the string posted.
  • Cookie value attacks
    Finally, click Put attack in cookie to put an attack string in a browser cookie. This cookie value is then sent back to the server. If you change your mind, clear the cookie value by using Clear cookie. The danger is that this value is not detected until something else (for example, clicking Submit or trying an illegal path) sends a request to the server. To avoid the dangerous cookie value being detected on every request, the page that tells the user that an attack was detected will also clear the cookie value.

Now that you see the attacks that are detected, let's look at the code and see how it is done.

Detect attempts to access invalid pages

The first step is to detect attempts to access various invalid pages. This will detect mapping attempts, which are a normal prelude to attack.

The Express application package makes this step trivial. In Express, you register handlers to paths, and those handlers can either process requests completely or do partial processing (for example, parse the cookies in the request) and let the process continue. Handlers are processed in the order that they are declared.

To get all the requests that don't have a page that is attached, simply register a handler at the end. If there is already a default handler that returns 404 page unavailable errors, add the attack detection code to that handler.

app.use("/", function(req, res) {
	res.send("Attempt to access " + req.path + " duly noted." + goBack);

Note: Not all mapping attempts are malicious. Search engines typically map websites to be able to link to their various pages. You can cause legitimate search engines to leave your site alone by using a robots.txt file.

Attackers are not going to try to access every possible path, since there are too many. Even if limited to only letters and only eight characters, there are 26^8≈2·10^11 possibilities. If the attacker can send 10,000 requests in a second, it would still take over 230 days to check all of them.

Instead, hackers scan the web pages that they get legitimately from the application, and look for URLs to show them what else they can scan. To cause them to scan pages that are invalid, put in your HTML code links that legitimate users would never see.

For example, you can use code that renders the link invisible:

<a href="should_be_invisible.html" style="color: white; background-color: white">Invisible</a>

If you use the Angular framework for client-side programming, you can use ng-if to specify conditions that would never happen for the invalid links. The advantage of these conditions is that they can be difficult for a scanner to evaluate. For example, without tracking all the scope variables, the scanner won't know that there is no variable called no-such-var, and therefore that variable's value would never be 'hello'.

    <div ng-if="false">
    	<a href="index_false.html">Don't go here</a>.
    <a href="index_1_is_2.html" ng-if="1==2"">Link to nowhere.</a>
	<a href="index_complex_false.html" ng-if="no-such-var == 'hello'">Another useless link.</a>

Detect attempts to use invalid values for code injection

A lot of attacks rely on providing invalid data. For example, a SQL injection relies on using the SQL string terminator, ', followed either by "or" (for a condition that is always true, for example, correctPassword='<input value>' or 1=1), "and" (for a condition that is always false, for example, <condition> and 1=0), or a semicolon (for a batched command, as in this XKCD cartoon).

Here is how you detect such attacks:

  1. Create an array of regular expressions for the various forms of code injection. This list is a subset for demonstration purposes:
    // Regular expressions that indicate attacks
    var inputAttacks = [
    	{ title: "SQL Batch Injection", regex: /'.*;/},
    	{ title: "SQL Or Injection", regex: /'.*or/i},
    	{ title: "SQL And Injection", regex: /'.*and/i},	
    	{ title: "File Inclusion", regex: /\.\.\//},
    	{ title: "File Inclusion", regex: /^\//}
  2. Put in a middleware call that looks for dangerous strings in the three places they are likely to be located: query parameters, post parameters, and cookies. These are the three places that include parameters that are passed from the browser to the server-side application. Other HTTP header fields, such as Content-Type and Encoding, are processed by the system software, which is less likely to contain the kinds of vulnerabilities whose exploits we are trying to detect.
    // Check all the body parameters for validity
    app.use("/", function(req, res, next) {
    	var key;
    	// Check all GET queries
    	for (key in req.query)
    		checkInputAttacks("query parameter " + key, req.query[key], req, res);
    	// Check all POST data
    	for (key in req.body)
    		checkInputAttacks("post parameter " + key, req.body[key], req, res);
    	// Check all cookies
    	for (key in req.cookies)
    		checkInputAttacks("cookie " + key, req.cookies[key], req, res);
    	// If we ran res.send (which checkInputAttacks does when it detects an attack),
    	// next() won't continue the processing anyway.
  3. Create a function that looks for all the attack regular expressions in strings to be called from the middleware.
    // Check for attacks in a string we got from the user.
    var checkInputAttacks = function(location, str, req, res) {
    	for(var i=0; i<inputAttacks.length; i++)
    		if (str.match(inputAttacks[i].regex)) {
    			logBadInputAttack(req, str, inputAttacks[i].title + " in the " + location);
    			res.send("Your " + inputAttacks[i].title + " attack in the " +
    				location + " has been noted." + goBack);
  4. Log attacks when they are discovered. In this sample application, the log is simply an array.
    // Log a bad input attack
    var logBadInputAttack = function(req, str, title) {
    	var entry = {};
    	entry.timeStamp = new Date();
    	entry.title = "Bad input: " + title; = req.originalUrl;
    	entry.value = str;
    	entry.attacker = req.ip;

Note this approach may suffer from false positives. For example, imagine that a user types the following in a description field: "This is the user's choice; the user can either apply this feature or ignore it"

The text matches the regular expression for a SQL batch attack: an apostrophe (') followed after a number of characters by a semicolon (;). If necessary, you can specify that for certain paths, certain fields are not to be checked. The req object includes the path. To make some fields exempt from checking, create lists of exempt fields (of all three types: get parameters, post parameters, and cookies):

// Fields that are not checked for the three field types
var exemptGet = [];
var exemptPost = ["/login:exempt"];
var exemptCookie = [];

Modify the call that checks the fields to first check if they are on the applicable exempt list, and if so, do not check them:

// Check all the body parameters for validity
app.use("/", function(req, res, next) {
	var key;

	// Check all GET queries
	for (key in req.query)
		if (exemptGet.indexOf(req.path + ":" + key) === -1)  // Don't check exempt fields
			checkInputAttacks('query parameter "' + key + '"', req.query[key], req, res);
	// Check all POST data
	for (key in req.body)
		if (exemptPost.indexOf(req.path + ":" + key) === -1)  // Don't check exempt fields	
			checkInputAttacks('post parameter "' + key + '"', req.body[key], req, res);

	// Check all cookies
	for (key in req.cookies)
		if (exemptCookie.indexOf(req.path + ":" + key) === -1)  // Don't check exempt fields	
			checkInputAttacks('cookie "' + key + '"', req.cookies[key], req, res);
	// If we ran res.send (which checkInputAttacks does when it detects an attack), 
	// next() won't continue the processing anyway.

Test the attack log

IBM Bluemix includes an AppScan service that runs a variety of attacks on your application to identify its vulnerabilities. To verify that attacks are logged, you can run it against your application. In the table below, you can see some of the results. Even this tiny web application detected 364 different attack attempts by AppScan.

TypeTimeAttacking IPPath
Invalid path 2015-11-27T03:43:03.481Z /badPath.html
Invalid path 2015-11-27T03:43:03.493Z /index_1_is_2.html
Bad input: SQL Batch Injection in the query parameter "uid" 2015-11-27T03:43:03.496Z /login?uid=%27+or+true%3B+--&passwd=
' or true; --
Bad input: SQL Or Injection in the query parameter "uid" 2015-11-27T03:43:03.496Z /login?uid=%27+or+true%3B+--&passwd=
' or true; --
Invalid path 2015-11-27T03:43:03.524Z /NonExistentFile
Bad input: SQL Batch Injection in the query parameter "uid" 2015-11-27T03:43:03.525Z /login?uid=%27+or+


Now you know how to detect attacks. Once an attack is detected, you can report it to Track & Plan, block access from that IP address, or take other steps to make the attack more difficult.

Downloadable resources

Related topics


Sign in or register to add and subscribe to comments.

ArticleTitle=Are you under attack? Detect attacks against Node.js applications