Skip to main content

How to protect your Node.js applications from malicious dependencies

You have probably heard about a recent incident where a popular npm package, event-stream, included malicious code that could have affected thousands of apps (or more!). Hopefully, the attack was tailored to affect only a specific project.
The original author of the library was the victim of a social engineering attack and a malicious hacker gained publishing permissions. Many people argue that the original author should have been more cautious.
But that’s not the real problem.
Why?
Because the original author of the library could have published the malicious code intentionally, anyone who owns a library could publish malicious code at any time. A lot of us are relying on the honor system, hoping that no one will publish malicious code.
How can we prevent that?
Well, there’s always going to be multiple ways of hacking the system and injecting malicious code into our apps. Not only through dependencies but also through unintentional vulnerabilities.
However, we can still think about how to prevent these things from happening but more importantly, we need to think about ways of mitigating their effects.

Prevention

There are some preventative actions you can take right now:
  • Lock your dependencies. Use package-lock.json or yarn.lockto prevent getting automatic updates when deploying (when doing npm/yarn installin your server). At least this way you will get fewer chances of getting a malicious update that the npm team hasn’t cleaned up yet. However, this wouldn’t have prevented the event-stream from affecting you since the malicious code was available in the npm registry for weeks. But it probably would have prevented you from a separate incident back in July.
  • Use npm auditSnyk and/or GitHub security alerts to be notified when any of your dependencies could contain security vulnerabilities.

Mitigation

Now, how can we mitigate the effects of an attack once it’s triggered?
Well, most attacks consist of stealing data, mining and sending back the results to a server, etc. So you could execute your Node.js with a user with very limited permissions: restrict filesystem access, configure iptables to restrict the application to only connect to certain domains, etc. The problem is that in the era of cloud services you probably can’t do that in your cloud provider.
Is there anything we can do inside Node.js?
The Node.js contributors have already started thinking about a Node.js Security Model. So, we can expect different levels of security to be implemented inside Node.js in the future.
I personally would love a permissions system where you could define what things you need to access in your package.json. For example:
{
  "permissions": {
    "fs": {
      "directories": {
        "$TEMP": "rw",
        "$SRC_ROOT": "r"
      }
    },
    "network": {
      "tcp": {
        "v4:*:$PORT": "LISTEN"
      }
    }
  }
}
This would be something like the Content Security Policy we have in modern browsers.
But of course, this is just my suggestion and the Node.js Security Model idea is just starting to be evaluated. Don’t expect an implementation in the near future.
So, is there something we can do right now? And more specifically, is there anything we can do in Userland without changing the Node.js internals?
The answer is yes!

Sandboxing your app — the hardcore way

Thanks to the dynamic nature of JavaScript that Node.js also follows, we are able to hack the runtime. We can:
  • Hijack the require() calls and manipulate the code that’s inside. That’s how ts-node/register and @babel/registerwork.
  • Run code in a sandboxed environment with the vm moduleand pass a custom require function that prevents accessing certain modules, or wraps core modules to prevent accessing certain things.
OR
  • Just override the core modules, directly. Let’s look at how we can do this:
I’m going to show a proof of concept of overriding readFileSyncto prevent accessing files in a specific directory. In practice, we should override a few other functions and we also have the option of whitelisting instead of blacklisting certain directories.
But as an example, I just want to prevent malicious code:
// malicious.js
const fs = require('fs')
const secrets = fs.readFileSync('/system/secrets.txt', 'utf8')
console.log(secrets);
I’m going to implement a cage.js file that overrides the fs core module and I’m going to intercept that function and prevent accessing files inside /system/:
// cage.js
const fs = require('fs')
const path = require('path')
const wrap = (module, name, wrapper) => {
  const original = module[name]
  module[name] = wrapper(original)
}
wrap(fs, 'readFileSync', (readFileSync) => (...args) => {
  const [filepath] = args
  const fullpath = path.resolve(filepath)
  if (fullpath.startsWith('/system/')) {
    throw new Error('You do not have permissions to access this file')
  }
  return readFileSync(...args)
})
// Prevent further changes
Object.freeze(fs)
Voilá! There it is. Now if we run the malicious code directly:
node malicious.js
We will see the contents of that file printed to the stdout. But if we tell Node.js to first run cage.js like this:
node -r cage.js malicious.js
We will see that the malicious code was not able to access the content of the file and an error was thrown.
Obviously, this is just a proof of concept. The next step would be to override more functions, make it configurable instead of hardcoding file paths, and, ideally, do the same with other core modules. For example overriding http(s).request .

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...