NodeJS - Abusing Lazy Loading Technique
Exploiting Lazy Loading technique for remote code execution
Table of Contents
Introduction
During vulnerability research on a containerized Node.js application, I discovered a critical arbitrary file overwrite vulnerability. Initial attempts to overwrite core application files like app.js proved ineffective due to two key constraints:
Application Restart Required: Modifying
app.jsrequired restarting the entire application.Container Reset Protection: Crashes triggered immediate container resets that restored all original files.
However, I discovered the developers were using lazy loading patterns throughout the application:
Example: Lazy Loading Pattern
async content() {
// Lazy loading - module loaded only when function is called
const { generate } = require('random-words');
const words = generate(5);
const sentence = words.join(' ');
return sentence;
}What is Lazy Loading
"Lazy loading is a technique where modules or dependencies are loaded only when they are needed, rather than during the application's startup phase. This approach minimizes memory usage and reduces startup time, especially in applications with numerous dependencies."
Node.js Module Caching Behavior
Node.js caches modules after their first require() call. Once a module is loaded and cached, subsequent require() calls return the cached version, even if the original file has been modified on disk. This behavior is crucial to our attack.
Exploitation Strategy
This gave me an idea: what if I could modify an uncached module to abuse the lazy loading technique and gain remote code execution?
After analyzing the application's front-end, I identified:
Unused API routes not accessible through the UI that use lazy loading.
Crash triggers - actions that would force container resets and clear cached modules.
Attack Vectors
Direct Lazy Loading
Overwrite lazy-loaded modules (avoiding core files like
app.js).Trigger the target endpoint to load the poisoned module.
Execute payload when Node.js loads the malicious code.
Clearing Cached Modules
For already-cached modules:
Crash the application intentionally to clear Node.js module cache.
Wait for the container to reset, clearing the cache.
Overwrite lazy-loaded modules (avoiding core files like
app.js).Trigger the target endpoint to load the poisoned module.
Execute payload when Node.js loads the malicious code.
Demonstration
To demonstrate this vulnerability, I've created a proof-of-concept environment consisting of:
A vulnerable Node.js Express application with file upload and crash endpoint.
Exploitation script showcasing different attack scenarios
Application source code
Start the application
Run the application inside a container

Vulnerable endpoint for file upload

Crash Endpoint
This crash endpoint is crucial for the cache-clearing attack vector, as it forces the container to restart and clear the Node.js module cache. This used in the second scenario.
Uncached lazy module Scenario #1
Since the content endpoint uses lazy module loading, it isn't called at application startup, leaving its module uncached in Node.js.

Overwrite the random-words to gain remote code execution
This script demonstrates successful exploitation when the module hasn't been cached yet.

Evidence

Clearing Cached Modules #2
Abusing both Lazy Modules and container mechanisms by Crashing to Clear the Module Cache.
Note: Reset the application container to return the old state after scenario #1.
Call the content endpoint first to ensure the module is cached

Evidence for the unexploitable scenario

This final script demonstrates the complete attack chain: triggering a cached module, failing to exploit, then crashing the container to clear the cache and successfully exploiting:
Evidence for RCE after crashing the application and container reset


the modified script was loaded successfully after crashing to clear the cached modules.
Last updated
Was this helpful?