In Memory Backdoor for Node.js Express Apps

Earlier this week Zach Grace published an article on one way that you could backdoor a Node.js Express application without touching disk. This jogged my memory of something I posted in our team’s chat this last week but never wrote about; how I would in memory backdoor an express application. It’s a bit different than how Zach approached it so I thought it would be good to expand upon his post sharing the knowledge.

My “vulnerable” proof of concept is below. It uses a fairly common pattern of putting routes in a separate file. The eval is for convenience of demonstration and isn’t a pattern we find frequently.

Server (index.js)

var express = require(express)
var routes = require(./routes);
var app = express()

app.use(/, routes);
app.listen(3000, function () {
 console.log(Example app listening on port 3000!)
})

routes.js

var express = require(express);
var router = express.Router();

router.route(/).get(function (req, res) {
 // Assume some sort of code execution here
 eval(req.query.code);
 console.log(completed);
 return res.send(‘’);
})

module.exports = router;

Now if we try and exploit and backdoor this app the way that Zach approached the problem we can’t for one simple reason, app is not defined. Now this won’t always be the case, but it’s very common for express applications to specify their routes in another file making app unavailable. Even if you call app=express(), as the express variable is available you get a different instance of express, so no go there.

What we do always, for sure, have access to are req (request) and res (response) but since we don’t know the names the developer used we’ll have to use what is always there, arguments.

The approach I thought to take was to find a route in the existing routing table (my example only has 1 route so we assume that but we would have to add some logic to find the route we wanted to backdoor, or maybe backdoor all of them ;). Then we wrap that route with our logic and we’re done.

Note: I’m sure with enough digging in the innards of req or res one could find a reference to app and perform the same attack that Zack mentioned.

Update (3/3/17, 9:53am): That didn't take long. Chris Foster pointed out that you can use arguments[0].app to get access to app consistently. Embarrassingly enough this is documented in the express docs.

Here is how you would find that route and it’s handler (function) and save it for later.

bd=arguments[0].route.stack[0].handle;
// same as req.route.stack[0].handle;

Then we want to replace the original handler with our function, but make sure we call the original.

arguments[0].route.stack[0].handle=function(req,res,next){
 console.log('0wned'); // exfiltrate things here
 bd(req,res,next)
}

Demo video showing this method for in-memory backdoor.

The reality here is that if you can get server side JavaScript injection on a node.js application it’s likely game over. You just don’t have the sandbox like you have with a browser. You will find one way or another to exfiltrate data, gain access to other parts of the applications or simply gain control of the server as the user that is running the node.js process.

There are runtime protections being explored for node but they are all still very early in development. See this paper from Microsoft Research

My take away here for all attackers is that context matters a lot for exploitation and be careful to not make assumptions about your execution environment when you are developing your payloads.

For more JavaScript internals read the “When This is really That” post by my co-worker Jon Lamendola.

You might also enjoy reading: