Kuboid
Open Luck·Kuboid.in
Black Hat2023
Open in YouTube ↗

HODOR: Reducing Attack Surface on Node.js via System Call Limitation

Black Hat693 views22:13about 2 years ago

This talk introduces HODOR, a lightweight runtime protection system designed to mitigate arbitrary code execution (ACE) attacks in Node.js applications by restricting system calls. The researchers utilize cross-language program analysis to construct call graphs across JavaScript and C++ layers, enabling the generation of precise system call whitelists. This approach significantly reduces the attack surface for Node.js applications and their dependencies, effectively mitigating a large percentage of potential exploits with minimal runtime overhead. The presentation details the implementation of this mechanism and its effectiveness against various attack vectors.

Why Your Node.js App Is One npm install Away From Arbitrary Code Execution

TLDR: Node.js applications frequently suffer from arbitrary code execution due to the massive, interconnected nature of the npm ecosystem and the way the runtime interacts with the underlying OS. Researchers at Black Hat 2023 introduced HODOR, a runtime protection system that uses cross-language call graph analysis to enforce strict system call whitelists. By restricting what the Node.js process can actually do at the kernel level, you can effectively neutralize entire classes of supply chain and injection attacks.

Node.js is the backbone of modern web infrastructure, but its design philosophy creates a massive security blind spot. Because the runtime is built on a single-threaded event loop that relies heavily on native C++ bindings, a vulnerability in a single dependency can grant an attacker full access to the underlying operating system. We see this constantly in bug bounty programs: a developer pulls in a package to handle a simple task, that package has a hidden dependency with a vulnerability, and suddenly the entire application is exposed to Injection attacks.

The core problem is that Node.js applications are rarely just JavaScript. They are a complex stack of JavaScript, C++ bindings, and native modules. When an attacker finds an entry point—like a deserialization flaw or a prototype pollution bug—they don't just stay in the JavaScript sandbox. They use the application's own legitimate functionality to trigger system calls that lead to command execution.

The Mechanics of the Attack Surface

The research presented at Black Hat highlights a grim reality: nearly 20% of packages in the npm ecosystem are vulnerable to some form of exploit, and many of these lead directly to arbitrary code execution. The attack flow is usually straightforward. An attacker identifies a vulnerable package, injects a malicious payload, and leverages the child_process module or similar built-in methods to execute arbitrary commands.

Consider a simple scenario where an attacker targets a package that uses exec to handle user input. If the input isn't sanitized, the attacker can break out of the intended logic:

const { exec } = require('child_process');
// Malicious input: "; cat /etc/passwd #"
exec('some_command ' + userInput);

Once the attacker has this level of control, they can establish a reverse shell, exfiltrate sensitive files, or pivot deeper into the network. The issue isn't just the code you write; it's the code you inherit. Because Node.js allows such deep interaction with the kernel, the attack surface is effectively the entire set of system calls available to the process.

Restricting the Kernel Interface

The HODOR research team proposed a shift in how we think about runtime security. Instead of trying to patch every individual dependency—which is a losing battle—they focused on restricting the system calls the Node.js process is allowed to make. By using LLVM and Clang to perform cross-language program analysis, they constructed precise call graphs that map out exactly which system calls are required for the application to function.

This is where the technical brilliance lies. They didn't just look at the JavaScript; they analyzed the C++ bindings and the dependency tree to generate a whitelist. If a system call isn't on the list, the kernel blocks it. This is enforced using Seccomp, a Linux kernel feature that allows a process to make a one-way transition into a state where it can only call a limited set of functions.

For a pentester, this changes the game. If you are testing an application protected by a system like this, your standard payloads will fail. You might successfully trigger an injection, but when your payload tries to call execve or socket, the kernel will simply kill the process or return an error. The research showed that this approach could mitigate over 70% of the tested exploits with negligible performance overhead.

Implementing Runtime Hardening

For those of us building or testing these systems, the takeaway is clear: we need to stop trusting the runtime to police itself. While HODOR is a research project, the principles are applicable today. You can use tools like strace to audit the system calls your application actually makes during normal operation. If your web server never needs to spawn a shell, why is it allowed to call execve?

Defenders should look into container-level security policies that restrict system calls. By default, most Node.js containers are running with far more privileges than they need. Using a tool like NodeProof or similar static analysis tools can help you identify which dependencies are pulling in dangerous native modules.

The next time you are on an engagement, don't just look for the injection point. Look at the environment. If you can identify a way to force the application to make a system call that it doesn't need, you've found a path to bypass standard application-level filters. The future of securing Node.js isn't in writing better sanitization functions; it's in locking down the interface between the application and the kernel. Start auditing your own dependencies today, and ask yourself: does this package really need the ability to spawn a shell? If the answer is no, you have a clear path to hardening your environment.

Talk Type
research presentation
Difficulty
advanced
Category
web security
Has Demo Has Code Tool Released


Black Hat Europe 2023

47 talks · 2023
Browse conference →
Premium Security Audit

We break your app before they do.

Professional penetration testing and vulnerability assessments by the Kuboid Secure Layer team. Securing your infrastructure at every layer.

Get in Touch
Official Security Partner
kuboid.in