Getting started
What I like to do when I start testing node apps, before doing anything else, is to look at the 3rd party installed dependencies of the app. All the dependencies should be listed in a file called package.json. Here is an example of what one of those files looks like:
{ "name": "express", "description": "Fast, unopinionated, minimalist web framework", "version": "4.9.8", "author": "TJ Holowaychuk <tj@vision-media.ca>", "contributors": [ "Aaron Heckmann <aaron.heckmann+github@gmail.com>", "Ciaran Jessup <ciaranj@gmail.com>", "Douglas Christopher Wilson <doug@somethingdoug.com>", "Guillermo Rauch <rauchg@gmail.com>", "Jonathan Ong <me@jongleberry.com>", "Roman Shtylman <shtylman+expressjs@gmail.com>", "Young Jae Sim <hanul@hanul.me>" ], "keywords": [ "express", "framework", "sinatra", "web", "rest", "restful", "router", "app", "api" ], "repository": "strongloop/express", "license": "MIT", "homepage": "http://expressjs.com/", "dependencies": { "accepts": "~1.1.2", "cookie-signature": "1.0.5", "debug": "~2.0.0", "depd": "0.4.5", "escape-html": "1.0.1", "etag": "~1.4.0", "finalhandler": "0.2.0", "fresh": "0.2.4", "media-typer": "0.3.0", "methods": "1.1.0", "on-finished": "~2.1.0", "parseurl": "~1.3.0", "path-to-regexp": "0.1.3", "proxy-addr": "~1.0.3", "qs": "2.2.4", "range-parser": "~1.0.2", "send": "0.9.3", "serve-static": "~1.6.4", "type-is": "~1.5.2", "vary": "~1.0.0", "cookie": "0.1.2", "merge-descriptors": "0.0.2", "utils-merge": "1.0.0" }, "devDependencies": { "after": "0.8.1", "istanbul": "0.3.2", "mocha": "~1.21.5", "should": "~4.0.4", "supertest": "~0.14.0", "ejs": "~1.0.0", "marked": "0.3.2", "hjs": "~0.0.6", "body-parser": "~1.8.2", "connect-redis": "~2.1.0", "cookie-parser": "~1.3.3", "express-session": "~1.8.2", "jade": "~1.6.0", "method-override": "~2.2.0", "morgan": "~1.3.1", "multiparty": "~3.3.2", "vhost": "~3.0.0" }, "engines": { "node": ">= 0.10.0" }, "scripts": { "prepublish": "npm prune", "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/", "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/" }
There are a lot of optional fields here, but the important ones are the dependencies. These give you a good idea of what the app is doing, as well as the version numbers of each dependency which can help with finding previously patched vulnerabilities.
If you run $ npm install
it will grab all of these dependencies and put them in a folder called node_modules. All files that exist inside the folder or sub-folders containing the node_modules folder can now import any of the modules inside node_modules.
Typically, each installed module has its own set of dependencies. Each module will recursively call $ npm install
and create a new node_modules folder inside its package folder; because of this, modules are frequently installed redundantly. Sorting through all the dependencies quickly becomes an impossible task and vulnerabilities often trickle down. For example, Express has a dependency called send.js. Send recently had a low level vulnerability which trickled down into Express, affecting all applications listing Express as a dependency. This includes a lot of applications.
Express
Chances are if you're pentesting a node app, it's running Express. Express is a micro web application framework very similar to python flask.
Session management
Express used to do it's own session management but recently it (and a lot of other built in express modules) got pulled out and split off.
Whether you're using the old session handling built into express or the new splinter library, chances are you're using a signed cookie store. As with Django and Flask apps, and other new hip web frameworks, these signed cookies require a secret key. An example is shown below.
javascript app.use(session({ keys: ['key'] }))
This secret key is EXTREMELY important to be kept secure, random, and long. If an attacker got her hands on the key she would be able to forge her own session signatures, which could lead to cookie forgery in the cookie store, and possibly remote code execution.
It should also be noted in the older versions of express these session stores were not encrypted by default, and all stored client side in the cookie. Any attribute you attached to a users session was visible to the user in his cookies. It's possible to find hidden secrets the developer thought he was storing securely in these session tokens. One example might be the users hashed password.
In the older version of express, if you didn't set an expiration flag, the signatures would always be the same as well, meaning each time a user logged in he had a high chance of having the same things put in his session store, thus getting always the same session token.
Express middleware
Express uses a pipelined middleware architecture. What this means is that a request comes in, a number of function hooks are called with 2 objects, a request object and a response object. Each function hook modifies these objects and calls the next hook with the new modified objects. The final hook is responsible for responding to the user.
A lot of these hooks are 3rd party installs. Below is one example.
passport
passport is a 3rd party express middleware module often used for authentication. It can handle OAuth, basic auth, and OpenID, among others. This hook can be executed against all incoming requests so when the developer defines the final response hook they already know if a user is authenticated.
Templating languages
Templating languages are big part of express and are made to be plug and play. An ideal templating language will automatically perform output encoding and reduce the chances of XSS dramatically. I would recommend all HTML be sent to users through a templating language that automatically deals with output encoding.
Debug mode
Express is very frequently ran in debug mode. Many developers do not even realize there is a production mode. Debug mode can lead to information disclosure.
The easiest way to check for it is to access the following URI https://app/%2
This will return a stack trace if the app is running in debug mode.
Things to grep for
First before grepping, I would recommend moving the node_module folder out of the project directory. This is because a lot of your grep results will show up in 3rd party apps, and they will probably be out of scope for your code review.
Math.random
In JavaScript, one way of generating random numbers is by calling Math.random. Math is a built in module. This is bad. Usually really bad. Math.random is not cryptographically secure. Because it's built into javascript, it's included in NodeJS, but anytime it's used it should not be in a place that needs a secure random number. Node has a built in crypto library for this very reason. It should be used for random number generation
crypto
Any time a developer is using the crypto library it should be looked at. There's a good chance whatever they're doing, it's done better in an existing 3rd party module. But regardless, hash functions, key signing, encryption and decryption are all handled through the built in crypto library.
Eval
Almost every developer knows the dangers of using the Eval function, but it's worth mentioning. Eval takes a string and parses it into valid javascript code, then executes the code. If used it can lead to json injection or remote code execution.
JSON.parse
Like eval, this takes a string and attempts to parse it into valid JSON. The use of this can lead to json injection.
Web sockets
socket.io is a very popular node module used for both front end and back end web socket communication. It's easy to use and it's also easy to miss if you're inspecting web traffic. There's a good chance things being pushed over socket are being parsed and on the application level, which can lead to a number of injection and XSS vulnerabilities that should be watched out for.
The npm registry
If a developer is doing something wonky, chances are there's already a node module that is out there doing it correctly. the npm registry is your best friend. You can find modules to suggest through there.